题解:P9962 [THUPC 2024 初赛] 一棵树
来点魔怔做法。
首先根据官方题解的思路,考虑树上背包,
发现这个东西具有下凸性,可以通过归纳法证明,那么我们就可以用带懒标记的可合并的堆维护凸包的斜率,具体的操作为,对每个结点先往堆里加入一个
写左偏树维护前
发现一个点被弹出堆的位置就是最近的子树中有
细节可以看代码。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5+5;
int n,k;
vector<int>vec[N];
int dep[N],f[N],fa[N],id[N],ed[N],dfn[N],dfcnt;
vector<int>e[N];
void dfs(int u)
{
dep[u] = dep[f[u]] + 1;
dfn[u] = ++dfcnt;
for(auto v:e[u]) if(v != f[u])
{
f[v] = u;
dfs(v);
}
ed[u] = dfcnt;
}
bool cmp(int i,int j){return dep[i] < dep[j];}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int tr[N];
int lowbit(int c){return c & (-c);}
void upd(int c,int x){for(;c <= n;c += lowbit(c)) tr[c] += x;}
int qry(int c){int Res = 0;for(;c;c -= lowbit(c)) Res += tr[c];return Res;}
vector<int>res;
int mn[N];
void dfs1(int u)
{
mn[u] = 1;
for(auto x:vec[u])
{
if(x < mn[u])
{
if(mn[u] != 1) res.push_back(mn[u]+2*(dep[u]-1));
mn[u] = x;
}
else
{
res.push_back(x+2*(dep[u]-1));
}
}
for(auto v:e[u]) if(v != f[u])
{
dfs1(v);
if(mn[v] == 1) continue;
int x = mn[v];
if(x < mn[u])
{
if(mn[u] != 1) res.push_back(mn[u]+2*(dep[u]-1));
mn[u] = x;
}
else
{
res.push_back(x+2*(dep[u]-1));
}
}
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);
cin>>n>>k;
for(int i = 1;i <= n;i++) id[i] = i;
for(int i = 1,u,v;i < n;i++)
{
cin>>u>>v;
e[u].push_back(v),e[v].push_back(u);
}
dfs(1);
sort(id+1,id+1+n,cmp);
for(int i = 1;i <= n;i++) upd(i,1),fa[i] = i;
for(int i = 1;i <= n;i++)
{
int u = id[i];
u = find(u);
//if(u%10000 == 0) cerr<<"!"<<u<<"\n";
while(u != 1)
{
if(qry(ed[u]) - qry(dfn[u]-1) > k/2) break;
fa[u] = f[u];
u = find(u);
}
upd(dfn[u],-1);
vec[u].push_back(-2*(dep[id[i]]-dep[u]));
}
if(k%2 == 0)
{
for(int i = 1;i <= n;i++) for(auto x:vec[i]) res.push_back(x+2*(dep[i]-1));
sort(res.begin(),res.end());
ll ans = 1ll*(n-1)*k;
for(int i = 0;i < k;i++) ans += res[i];
cout<<ans<<"\n";
return 0;
}
ll ans = 1ll*(n-1)*k;
dfs1(1);
res.push_back(mn[1]);
sort(res.begin(),res.end());
for(int i = 0;i < k;i++) ans += res[i];
cout<<ans<<"\n";
return 0;
}