hdu 6430 - DSU on tree(树上启发式合并)

题目链接:点击这里

 

解题思路:

根据树链剖分将子树分为重儿子和轻儿子。

那么我们的DSU实际是就是这个基础上的暴力了。对于一个子树。因为dfs肯定是先处理完儿子之后再处理父亲。那么我在处理一颗子树的时候如果它是他父亲的重儿子就保留它的结果,对于所有的轻儿子要消除影响。那么对于一颗子树我们每次都只要去暴力它的所有轻儿子就可以了。

时间复杂度:

对于u这个节点,wi是它的祖先,wj是wi的祖先。当处理wj这颗子树时,如果wi不是wj的重儿子,那么u的贡献就是被爆搜出来的。

就有:wi子树大小*2 < wj子树大小,这就说明u被爆搜的次数不会超过logn次。

所以时间复杂度为O(nlogn)

对于这题做法有一点的变化就是重儿子和轻儿子的求解方法不同,因为轻儿子的影响会被消除,所以在计数的时候cnt[x]不能只记0或1,但重儿子可以,因为他的结果不会被清空,而且儿子对父亲的cnt[x]贡献也只能是0或1,要不然它的最小公共祖先就不是它的父亲的。

cnt[x]表示对于某个u子树,u节点和他的儿子贡献的x个数和。

 

#include
using namespace std;
typedef long long ll;
const int mx = 1e5 + 10;
int n,m,v[mx],son[mx],siz[mx];
int ans[mx],cnt[mx],mark[mx],ret;
bool vis[mx];
vector  vec[mx],fac[mx];
void init()
{
	for(int i=1;i1) ans[ret] = max(ans[ret],u); 
		}else{ 
			if(cnt[u]&&!mark[u]) ans[ret] = max(ans[ret],u);
			if(!cnt[u]) cnt[u]++;
		}
	}
	if(x!=ret) visit(v[x],1);
	for(int u:vec[x]){
		if(vis[u]) continue;
		update(u,f,w);
		if(x==ret) unlock(u);
	}
}
void dfs2(int x,bool w)
{
	for(int v:vec[x]){
		if(v==son[x]) continue;
		dfs2(v,0);
	}
	if(son[x]) dfs2(son[x],1);
	vis[son[x]] = 1;
	ret = x,update(x,w,1);
	vis[son[x]] = 0;
	if(!w) update(x,w,-1);
}
int main()
{	
	init();
	while(~scanf("%d",&n)){
		int a,b;
		memset(ans,-1,sizeof(ans));
		for(int i=2;i<=n;i++){
			scanf("%d",&a);
			vec[a].push_back(i);
		}
		for(int i=1;i<=n;i++) scanf("%d",v+i);
		dfs1(1);
		dfs2(1,1);
		for(int i=1;i<=n;i++){
			printf("%d\n",ans[i]);
		}
	}
	return 0; 
} 

 

你可能感兴趣的:(树链剖分,DSU)