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

P6623 [省选联考 2020 A 卷] 树

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;
}
posted @ 2021-07-02 10:09  wljss  阅读(384)  评论(0编辑  收藏  举报