dsu on tree详解

介绍

这是个用来处理这样一类题目的:询问支持离线,并且询问与子树有关。

它可以很方便地在 O ( n l o g n ) O(nlogn) 的时间内完成。

正题

它是一个利用了重链剖分的一个性质的暴力。

先掏一道模板题来讲吧:

给出一棵节点有颜色的树,每次询问某个节点的子树内有多少种不同的颜色。

我们考虑离线做这个问题,先求出每个节点的答案,然后每次直接 O ( 1 ) O(1) 回答即可。

先考虑暴力,显然可以递归遍历一次整棵树,对于遍历到的每个节点,我暴力再统计一次它的子树信息然后求解,然后删去它的子树信息。我们思考一下,这个暴力劣在哪里?可以发现,对于一个节点 x x 的最后一个遍历到的儿子,我们不需要删去它的信息,它的信息可以直接给父亲用,然后父亲就只需要统计其他的儿子的子树内的信息就够了。

那么根据贪心的思想,我们肯定要让最后一个遍历到的儿子的子树尽可能大,那么显然这个儿子就是 x x 重儿子了。

然后就做完了。(没想到吧,就这么简单)

读者:滚蛋,这不还是个暴力吗,怎么证明时间复杂度?

好吧,接下来证明一下这样做暴力的时间复杂度是 O ( n l o g n ) O(nlogn) 的。

我们考虑每个节点的信息会被统计的次数:在这个节点到根节点的路径上,每有一条轻边,这个节点就会被统计一次。

因为重链剖分可以保证,任意一个节点到根节点的路径上轻边的数量不超过 log n \log n 条,所以总的时间复杂度是 O ( n l o g n ) O(nlogn) 的。

代码:

#include <cstdio>
#include <cstring>
#define maxn 100010

int n,m,col[maxn];
struct edge{int y,next;};
edge e[maxn*2];
int first[maxn];
void buildroad(int x,int y)
{
	static int len=0;
	e[++len]=(edge){y,first[x]};
	first[x]=len;
}
int size[maxn],mson[maxn];
void dfs1(int x,int fa)//求重儿子
{
	size[x]=1;
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==fa)continue;
		dfs1(y,x);
		if(size[y]>size[mson[x]])mson[x]=y;
		size[x]+=size[y];
	}
}
int tong[maxn],ans[maxn],now_ans=0;
void go(int x,int fa,int type)
{
	tong[col[x]]+=type;
	if(type==1&&tong[col[x]]==1)now_ans++;
	if(type==-1&&tong[col[x]]==0)now_ans--;
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa)go(e[i].y,x,type);
}
void dfs2(int x,int fa,bool del)
//求解,del表示求完x的子树的答案后需不需要清空x的子树的信息
{
	for(int i=first[x];i;i=e[i].next)//先统计轻儿子的答案
	if(e[i].y!=fa&&e[i].y!=mson[x])dfs2(e[i].y,x,true);
	if(mson[x]!=0)dfs2(mson[x],x,false);//最后统计重儿子的答案
	
	tong[col[x]]++;if(tong[col[x]]==1)now_ans++;//统计自己以及轻子树的信息
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa&&e[i].y!=mson[x])go(e[i].y,x,1);
	ans[x]=now_ans;//得到自己的答案
	
	if(del)go(x,fa,-1);//假如要删掉自己的信息,就暴力地删掉
}

int main()
{
	scanf("%d",&n);
	for(int i=1,x,y;i<n;i++)
	scanf("%d %d",&x,&y),buildroad(x,y),buildroad(y,x);
	for(int i=1;i<=n;i++)
	scanf("%d",&col[i]);
	dfs1(1,0);
	dfs2(1,0,false);
	scanf("%d",&m);
	for(int i=1,x;i<=m;i++)
	scanf("%d",&x),printf("%d\n",ans[x]);
}

例题2

题目传送门

还是一颗节点有颜色的树,需要统计每个棵子树内出现次数最多的颜色之和。

板子题,直接上代码:

#include <cstdio>
#include <cstring>
#define maxn 100010

int n,col[maxn];
struct edge{int y,next;};
edge e[maxn*2];
int first[maxn];
void buildroad(int x,int y)
{
	static int len=0;
	e[++len]=(edge){y,first[x]};
	first[x]=len;
}
int size[maxn],mson[maxn];
void dfs_getmson(int x,int fa)
{
	size[x]=1;
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==fa)continue;
		dfs_getmson(y,x);
		if(size[y]>size[mson[x]])mson[x]=y;
		size[x]+=size[y];
	}
}
long long ans[maxn],now_ans=0;
int tong[maxn],max_col=0;
void go(int x,int fa,int type)
{
	tong[col[x]]+=type;
	if(type==1)
	{
		if(tong[col[x]]>max_col)max_col=tong[col[x]],now_ans=col[x];
		else if(tong[col[x]]==max_col)now_ans+=col[x];
	}
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa)go(e[i].y,x,type);
}
void dfs_getans(int x,int fa,bool del)
{
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa&&e[i].y!=mson[x])dfs_getans(e[i].y,x,true);
	
	if(mson[x]!=0)dfs_getans(mson[x],x,false);
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa&&e[i].y!=mson[x])go(e[i].y,x,1);
	
	tong[col[x]]++;
	if(tong[col[x]]>max_col)max_col=tong[col[x]],now_ans=col[x];
	else if(tong[col[x]]==max_col)now_ans+=col[x];
	
	ans[x]=now_ans;if(del)go(x,fa,-1),max_col=0,now_ans=0;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&col[i]);
	for(int i=1,x,y;i<n;i++)
	scanf("%d %d",&x,&y),buildroad(x,y),buildroad(y,x);
	dfs_getmson(1,0);
	dfs_getans(1,0,false);
	for(int i=1;i<=n;i++)
	printf("%lld ",ans[i]);
}
发布了234 篇原创文章 · 获赞 100 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/102967706