开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情
【ICPC】2022西安站 L. Tree | DFS序、线段树、删点维护树上点的深度
本题我写得有点丑但是 VP 中过了于是还是写了题解qwq
推荐阅读滁生大佬写的同一道题的题解:2022ICPC西安站 L. Tree - 掘金 (juejin.cn)
题目链接
题目
题目大意
有一颗具有 个节点树,树以节点 为根。把树上的节点划分到若干个集合里,使得每个节点在且仅在一个集合中,任一集合 满足下述两个条件之一:
- 对于两个节点 ,要么 在以 为根的子树中,要么 在以 为根的子树中。
- 对于两个节点 , 不在以 为根的子树中并且 不在以 为根的子树中。
最小化划分的点集的数量。
思路
满足第一个条件的集合,其中的所有点都在一条从根节点到叶子节点的链上。如果全部划分成第一种集合,一共需要划分出叶子节点数个集合。
满足第二个条件的集合,其中所有的点都不在集合中其他点到根节点的路径上。如果全部划分成第二种集合,一共需要划分出树的层数个集合。
所以我们可以枚举第一种集合的数量,求第二种集合的数量更新答案。即:
- 由长到短选出当前树的最长链,将最长链上的点划分到一个集合中。
- 剩余的点划分成第二种集合的数量为显然为次长链的长度。
- 此时我们就获得了一个合法的划分方案。我们可以把最长链删去重复上述过程。
删最长链实际上可以对树进行长链剖分然后对所有链的长度进行排序依次枚举。不知道我赛中脑子跑去哪里了写 DFS 序后用线段树维护点的深度……但是既然 AC 了还是放出我的代码吧qaq
代码
#include <bits/stdc++.h>
using namespace std;
using LL=long long;
const int N=1000001;
vector<int> e[N];
int f[N],rk[N],dep[N],adr[N],siz[N],idk,n;
int del[N];
struct asdf{
int id,maxdep,v;
}s[N<<2];
void dfs(int u,int fa)
{
rk[u]=++idk;
adr[idk]=u;
siz[u]=1;
for (auto v:e[u])
{
if (v==fa) continue;
dep[v]=dep[u]+1;
dfs(v,u);
siz[u]+=siz[v];
}
}
void build(int l,int r,int rt)
{
if (l==r)
{
s[rt]={adr[l],dep[adr[l]],0};
return;
}
build(l,l+r>>1,rt<<1);
build((l+r>>1)+1,r,rt<<1|1);
if (s[rt<<1].maxdep>s[rt<<1|1].maxdep) s[rt]=s[rt<<1];
else s[rt]=s[rt<<1|1];
}
void change(int l,int r,int rt,int x,int y)
{
if (x<=l&&r<=y)
{
s[rt].maxdep--;
s[rt].v--;
return;
}
s[rt<<1].v+=s[rt].v;
s[rt<<1|1].v+=s[rt].v;
s[rt<<1].maxdep+=s[rt].v;
s[rt<<1|1].maxdep+=s[rt].v;
if (x<=(l+r>>1)) change(l,l+r>>1,rt<<1,x,y);
if (y>(l+r>>1)) change((l+r>>1)+1,r,rt<<1|1,x,y);
if (s[rt<<1].maxdep>s[rt<<1|1].maxdep) s[rt]=s[rt<<1];
else s[rt]=s[rt<<1|1];
s[rt].v=0;
}
int solve()
{
idk=0;
for (int i=1;i<=n;++i)
{
f[i]=del[i]=0;
e[i].clear();
siz[i]=dep[i]=adr[i]=rk[i]=0;
}
del[0]=1;
scanf("%d",&n);
for (int i=2;i<=n;++i)
{
scanf("%d",&f[i]);
e[f[i]].push_back(i);
}
dep[1]=1;
dfs(1,0);
build(1,n,1);
int ans=s[1].maxdep;
for (int i=1;i<=n;++i)
{
asdf t=s[1];
if (s[1].maxdep<=0) break;
for (int j=t.id;!del[j];j=f[j])
{
del[j]=1;
change(1,n,1,rk[j],rk[j]+siz[j]-1);
}
if (s[1].maxdep < 0) break;
ans=min(ans,i+s[1].maxdep);
}
return ans;
}
int main()
{
int T;
for (scanf("%d",&T);T--;)
printf("%d\n",solve());
return 0;
}