Dsu on Tree总结

Dsu on Tree(树上启发式合并)

关于Dsu?

并查集(亦称Ufs)。

然而本算法和并查集并没有半毛钱关系。

有什么用?

可以在\(O(nlogn)\)的时间复杂度内解决大部分不带修改的子树信息查询问题。

算法过程?

一个dfs,可能还需要写一个辅助函数Add。

简述一下算法过程:

假设现在dfs到了\(x\)结点。

  1. dfs进入轻子树,离开这棵轻子树后从统计数组中暴力清除这棵轻子树中的信息(用另一个dfs(即Add,参数\(kk=-1\))递归遍历整棵轻子树并从统计数组中清除每个结点的信息)。

  2. dfs进入重子树,结束后不清除这棵重子树的信息(可以认为\(x\)继承了其重儿子\(heavychild[x]\)的信息)。

  3. 用另一个dfs(即Add,参数\(kk=+1\))暴力统计x的所有轻子树的信息并计入统计数组。

  4. 此时统计数组内的信息即为以\(x\)为根的子树的信息,此时可以回答所有关于子树\(x\)的询问。

这个算法好暴力!

辣鸡博主别欺负我读书少,这不是\(O(n^2)\)的吗?

别急,我们来冷静分析一下这个算法的时间复杂度。

显然,如果只考虑dfs1和的dfs2(不考虑其中的Add),这个算法显然是\(O(n)\)的。

现在我们来看看一个结点\(x\)会被哪些结点出发的Add递归访问到。

显然如果结点\(x\)会被从结点\(y\)出发的Add递归访问到,\(y\)必须是\(x\)到根路径上的结点。

分析算法过程,我们可以发现,Add的出发节点必须作为其父结点的一个轻儿子,即只可能是其所在重链的顶端结点。

考虑到任何一个结点到根结点的路径上最多只有\(logn\)条重链,也就是最多有\(logn\)个合法的Add的出发结点,因此每个结点最多被Add访问到\(O(logn)\)次。自然,算法总时间复杂度\(O(nlogn)\)得证。

程序实现?

dfs1求出每个结点的重儿子

这里的实现方法和树剖完全相同。

void dfs1(int x,int depth){
	dep[x]=depth;
	siz[x]=1;
	int maxsiz=-1;
	trav(i,x){
		int ver=e[i].to;
		dfs1(ver,depth+1);
		siz[x]+=siz[ver];
		if(siz[ver]>maxsiz){
			maxsiz=siz[ver];
			pc[x]=ver;
		}
	}
}
dfs2统计答案
void Add(int x,int kk){
	if(kk==1){
		统计这个结点的贡献;
	}
	else if(kk==-1){
		清除这个结点的贡献;
		(这里可以直接清空深度为dep[x]的统计数组)
	}
	trav(i,x){
		int ver=e[i].to;
		if(mark[ver]) continue;
		Add(ver,kk);
	}
}

void dfs2(int x,bool Keep){
	trav(i,x){
		int ver=e[i].to;
		if(ver==pc[x]) continue;
		dfs2(ver,0);
	}//1
	if(pc[x]) dfs2(pc[x],1);//2
	mark[pc[x]]=1;
	Add(x,1);//3(这里通过标记重儿子可防止Add进入x的重子树,相当于以下注释代码)
//	trav(i,x){
//		int ver=e[i].to;
//		if(ver==pc[x]) continue;
//		Add(ver,1);
//	}
	mark[pc[x]]=0;
	rin(i,0,(int)vec[x].size()-1)
		ans[vec[x][i].id]=...;//4(用std::vector储存结点x上的询问
	if(!Keep) Add(x,-1);//这里不能memset,否则会TLE
}

一些其它的写法(摘自Codeforces

vector<int> *vec[maxn];
int cnt[maxn];
void dfs(int v, int p, bool keep){
	int mx = -1, bigChild = -1;
	for(auto u : g[v])
	   if(u != p && sz[u] > mx)
		   mx = sz[u], bigChild = u;
	for(auto u : g[v])
	   if(u != p && u != bigChild)
		   dfs(u, v, 0);
	if(bigChild != -1)
		dfs(bigChild, v, 1), vec[v] = vec[bigChild];
	else
		vec[v] = new vector<int> ();
	vec[v]->push_back(v);
	cnt[ col[v] ]++;
	for(auto u : g[v])
	   if(u != p && u != bigChild)
		   for(auto x : *vec[u]){
			   cnt[ col[x] ]++;
			   vec[v] -> push_back(x);
		   }
	//now (*cnt[v])[c] is the number of vertices in subtree of vertex v that has color c. You can answer the queries easily.
	// note that in this step *vec[v] contains all of the subtree of vertex v.
	if(keep == 0)
		for(auto u : *vec[v])
			cnt[ col[u] ]--;
}
int cnt[maxn];
void dfs(int v, int p, bool keep){
	int mx = -1, bigChild = -1;
	for(auto u : g[v])
	   if(u != p && sz[u] > mx)
		  mx = sz[u], bigChild = u;
	for(auto u : g[v])
		if(u != p && u != bigChild)
			dfs(u, v, 0);  // run a dfs on small childs and clear them from cnt
	if(bigChild != -1)
		dfs(bigChild, v, 1);  // bigChild marked as big and not cleared from cnt
	for(auto u : g[v])
	if(u != p && u != bigChild)
		for(int p = st[u]; p < ft[u]; p++)
		cnt[ col[ ver[p] ] ]++;
	cnt[ col[v] ]++;
	//now cnt[c] is the number of vertices in subtree of vertex v that has color c. You can answer the queries easily.
	if(keep == 0)
		for(int p = st[v]; p < ft[v]; p++)
		cnt[ col[ ver[p] ] ]--;
}

例题?

CF570D Tree Requests

代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <vector>
#define rin(i,a,b) for(int i=(a);i<=(b);i++)
#define rec(i,a,b) for(int i=(a);i>=(b);i--)
#define trav(i,a) for(int i=head[(a)];i;i=e[i].nxt)
using std::cin;
using std::cout;
using std::endl;
typedef long long LL;

inline int read(){
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x;
}

const int MAXN=500005;
int n,m;
int ecnt,head[MAXN];
int dep[MAXN],siz[MAXN],pc[MAXN];
int cnt[MAXN][30],cnt1[MAXN];
bool mark[MAXN],ans[MAXN];
char ch[MAXN];
struct Edge{
	int to,nxt;
}e[MAXN<<1];
struct Quest{
	int d,id;
};
std::vector<Quest> vec[MAXN];

inline void add_edge(int bg,int ed){
	ecnt++;
	e[ecnt].to=ed;
	e[ecnt].nxt=head[bg];
	head[bg]=ecnt;
}

void dfs1(int x,int depth){
	dep[x]=depth;
	siz[x]=1;
	int maxsiz=-1;
	trav(i,x){
		int ver=e[i].to;
		dfs1(ver,depth+1);
		siz[x]+=siz[ver];
		if(siz[ver]>maxsiz){
			maxsiz=siz[ver];
			pc[x]=ver;
		}
	}
}

void Add(int x,int kk){
//	if(kk==1){
		cnt[dep[x]][ch[x]-'A']+=kk; 
		if(cnt[dep[x]][ch[x]-'A']&1) cnt1[dep[x]]++;
		else cnt1[dep[x]]--;
//	}
//	else{
//		cnt[dep[x]][ch[x]-'A']=0;
//		cnt1[dep[x]]=0;
//	}
//这里去掉注释也对 
	trav(i,x){
		int ver=e[i].to;
		if(mark[ver]) continue;
		Add(ver,kk);
	}
}

void dfs2(int x,bool Keep){
	if(pc[x]) dfs2(pc[x],1);
	mark[pc[x]]=1;
	Add(x,1);
	mark[pc[x]]=0;
	trav(i,x){
		int ver=e[i].to;
		if(ver==pc[x]) continue;
		dfs2(ver,0);
	}
	rin(i,0,(int)vec[x].size()-1)
		ans[vec[x][i].id]=(cnt1[vec[x][i].d]<2);
//	if(!Keep){
//		memset(cnt,0,sizeof cnt);
//		memset(cnt1,0,sizeof cnt1);
//	}
//这里去掉注释并把下面加上注释也对,但是会T 
	if(!Keep) Add(x,-1);
}

int main(){
	n=read(),m=read();
	rin(i,2,n){
		 int u=read();
		 add_edge(u,i);
	}
	rin(i,1,n){
		char temp=getchar();
		while(!isalpha(temp)) temp=getchar();
		ch[i]=temp;
	}
	rin(i,1,m){
		int x=read(),d=read();
		vec[x].push_back((Quest){d,i});
	}
	dfs1(1,1);
	dfs2(1,1);
	rin(i,1,m){
		if(ans[i]) printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

posted on 2018-11-22 20:13  ErkkiErkko  阅读(81)  评论(0编辑  收藏

统计