dsu on tree详解
dsu 中文名是并查集,然而除了启发式合并,这个算法和并查集并没啥关系。
适用范围:
1.没有修改
2.询问子树
流程
暴力显然\(O(n^2)\) O(TLE)
先分析一下暴力的思路,我们是对每一棵子树做O(n)的统计
更确切地说,我们是 对一个节点的儿子的那棵子树做统计,清空,下一个儿子,清空,下下一个儿子,清空......该节点的树做统计,清空
我们发现,在 对该节点的树做统计 前,最后一个儿子的那棵子树统计不用清空
并且,这个不用清空的儿子size越大,显然越优
然后,这就是dsu on tree了
时间复杂度O(nlogn)
核心代码
void dfs2(int x,int fa,int opt)
{
for(int i=head[x];i;i=nt[i])
if(to[i]!=fa&&to[i]!=son[x])dfs2(to[i],x,0);
if(son[x])dfs2(son[x],x,1),Son=son[x];
ADD(x,fa,1);Son=0;ans[x]=nowans;
if(!opt)ADD(x,fa,-1),nowans=0,mx=0;
}
时间复杂度
显然,一个节点到根的所有边中,轻边不会超过log n条
考虑每个点的贡献
1、通过轻边被访问到。根据前面的性质,被访问次数 < log n
2、通过重边被访问到。显然只有一次
总体 O(nlog n)
总结
先想出一个关于每个节点的子树的暴力
然后套上dsu on tree,把复杂度的一个n变成log n
练习题
还有好多水题,自己做吧
CF600E 题解
CF570D 题解
CF208E听说各种方法都能过,主流dsu或差分
CF246E同上
CF1009F裸题
CF375D
wannafly Day2 E dsu+set
牛客练习赛60E
ccpc2020长春站F题
洛谷P4149
牛客练习赛81D
CF741D
2020省选D2T2现场的时候能用这算法水60分,具体思路就是对二进制下每一位分别处理,处理的时候用dsu on tree+树状数组
upd:貌似能拿100分,可能是我的常数太大了。。。
代码如下
#include<iostream>
#include<cstdio>
#define LL long long
using namespace std;
int n,tot;
const int N=530010;
int v[N],fa[N],head[N],to[N],nt[N];
void add(int f,int t)
{
to[++tot]=t;nt[tot]=head[f];head[f]=tot;
}
namespace solve1
{
LL ans;
int val[N];
void dfs(int x,int dep,int FA)
{
val[FA]^=(v[x]+dep);
for(int i=head[x];i;i=nt[i])dfs(to[i],dep+1,FA);
}
void work()
{
for(int i=1;i<=n;++i)dfs(i,0,i),ans+=val[i];
cout<<ans;
}
}
namespace solve2
{
LL ans;int sum,Son,now,bg;
int siz[N],son[N];
struct SZSZ
{
int tr[N<<2],ling;
int lowbit(int x){return x&(-x);}
void add(int pos,int v)
{
if(pos==0){ling+=v;return;}
for(;pos<=bg;pos+=lowbit(pos))tr[pos]+=v;
}
int ask(int pos)
{
if(pos<0)return 0;
int res=ling;
for(;pos;pos-=lowbit(pos))res+=tr[pos];
return res;
}
int wen(int l,int r)
{
if(l<=r)return ask(r)-ask(l-1);
else return ask(r)+ask(bg)-ask(l-1);
}
}A;
void dfs0(int x,int dep)
{
v[x]+=dep;siz[x]=1;
for(int i=head[x];i;i=nt[i])
{
dfs0(to[i],dep+1);
siz[x]+=siz[to[i]];
if(siz[to[i]]>siz[son[x]])son[x]=to[i];
}
}
void suan(int x,int opt)
{
A.add(v[x]&bg,opt);
for(int i=head[x];i;i=nt[i])
if(to[i]!=Son)suan(to[i],opt);
}
void dfs(int x,int dep,int opt)
{
for(int i=head[x];i;i=nt[i])
if(to[i]!=son[x])dfs(to[i],dep+1,0);
if(son[x])dfs(son[x],dep+1,1),Son=son[x];
suan(x,1);
if(A.wen(((1<<(now-1))+dep)&bg,(bg+dep)&bg)&1)++sum;
if(!opt)Son=0,suan(x,-1);
}
void work()
{
dfs0(1,1);
int mx=0;
for(int i=1;i<=n;++i)mx=max(mx,v[i]);
for(int i=1;(1<<(i-1))<=mx+1;++i)
{
sum=0;now=i;bg=(1<<i)-1;
dfs(1,1,0);
ans+=(LL)sum*(1<<(i-1));
}
cout<<ans;
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;++i)scanf("%d",&v[i]);
for(int i=2;i<=n;++i)scanf("%d",&fa[i]),add(fa[i],i);
if(n<=3000)solve1::work();
else solve2::work();
return 0;
}