dsu on tree学习笔记

一些废话

膜拜托神@Tony102

介绍

这是一个优雅的暴力,好写。

用于解决和子树相关静态问题的好东西。

看似暴力,其实优雅。

当然和并查集的关系并不大。

梦开始的地方

还有一个Explanation

一般过程

首先dfs1一遍找出重儿子,然后dfs2统计答案。

这里有两种写法,可以根据情况选择。一种是递归的,一种是直接枚举的。

个人认为枚举会快一些。

递归

inline void dfs1(int st,int fa){
	sz[st]=1,f[st]=fa;
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==fa) continue;
		dfs1(ed,st);
		sz[st]+=sz[ed];
		if(sz[ed]>sz[son[st]]) son[st]=ed;
	}
	return;
}
inline void add_val(int st,int ban){
    //这里是一个计算统计答案的地方
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==f[st]||ed==ban) continue;
		add_val(ed,ban);
	}
	return;
}
inline void del_val(int st){
	//这里是消除影响的地方
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==f[st]) continue;
		del_val(ed);
	}
	return;
}
inline void dfs2(int st){
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==f[st]||ed==son[st]) continue;
		dfs2(ed),del_val(ed);
	}
	if(son[st]) dfs2(son[st]);
	add_val(st,son[st]);
	ans[st]=sum;
	return;
}

枚举子树

inline void dfs1(int st,int f){
	sz[st]=1,dfn[++tot]=st,L[st]=tot,fa[st]=f;
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==fa[st]) continue;
		dfs1(ed,st);
		sz[st]+=sz[ed];
		if(sz[ed]>sz[son[st]]) son[st]=ed;
	}
	R[st]=tot;
	return;
}
inline void dfs2(int st){
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==fa[st]||ed==son[st]) continue;		
		dfs2(ed);
        for(int j=L[ed];j<=R[ed];j++) //消除影响
	}
	if(son[st]) dfs2(son[st]);
    //如果有需要的话这里要加上点st的影响
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==fa[st]||ed==son[st]) continue;
        //这里统计答案
	}
	return;
}

具体实现还是要看题目,但是一般不会有太大变动。

一些题目

CF600E

Luogu

CF

Sol

模板题。

统计答案时判断当前颜色出现次数是否大于||等于最多次数,分别讨论即可。

Code

inline void dfs1(int st,int fa){
	sz[st]=1,f[st]=fa;
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==fa) continue;
		dfs1(ed,st);
		sz[st]+=sz[ed];
		if(sz[ed]>sz[son[st]]) son[st]=ed;
	}
	return;
}
inline void add_val(int st,int ban){
	++cnt[c[st]];
	if(cnt[c[st]]>mx) mx=cnt[c[st]],sum=c[st];
	else if(cnt[c[st]]==mx) sum+=c[st];//加贡献
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==f[st]||ed==ban) continue;
		add_val(ed,ban);
	}
	return;
}
inline void del_val(int st){
	--cnt[c[st]];//消除影响
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==f[st]) continue;
		del_val(ed);
	}
	return;
}
inline void dfs2(int st){
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==f[st]||ed==son[st]) continue;
		dfs2(ed),del_val(ed),sum=mx=0;
	}
	if(son[st]) dfs2(son[st]);
	add_val(st,son[st]);
	ans[st]=sum;//记录答案
	return;
}

CF357D

Luogu

CF

Sol

利用树状数组统计有关颜色的信息即可。

注意树状数组不可以有0,而且题目中\(k>0\),所以颜色数等于0时就不需要算进答案里。

统计的时候判断一下0。

Code

namespace BIT{
	int c[N];
	inline void addt(int pos,int val){
		for(int i=pos;i<=n;i+=(i&(-i))) c[i]+=val;
	}
	inline int sumt(int pos){
		int res=0;
		for(int i=pos;i;i-=(i&(-i))) res+=c[i];
		return res;
	}
};
using BIT::addt;
using BIT::sumt;
inline void dfs1(int st,int fa){
	sz[st]=1,f[st]=fa;
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==fa) continue;
		dfs1(ed,st);
		sz[st]+=sz[ed];
		if(sz[ed]>sz[son[st]]) son[st]=ed;
	}
	return;
}
inline void add_val(int st,int ban){
	if(cnt[c[st]]>0) addt(cnt[c[st]],-1);
	addt(++cnt[c[st]],1);
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==f[st]||ed==ban) continue;
		add_val(ed,ban);
	}
	return;
}
inline void del_val(int st){
	addt(cnt[c[st]],-1);
	if(cnt[c[st]]>1) addt(cnt[c[st]]-1,1);
	--cnt[c[st]];
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==f[st]) continue;
		del_val(ed);
	}
	return;
}
inline void dfs2(int st){
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==f[st]||ed==son[st]) continue;
		dfs2(ed),del_val(ed);
	}
	if(son[st]) dfs2(son[st]);
	add_val(st,son[st]);
	for(int i=0;i<(int)qs[st].size();i++){
		int id=qs[st][i].first,k=qs[st][i].second;
		ans[id]=sumt(n)-sumt(k-1);
	}
	return;
}

CF208E

Luogu

CF

Sol

用0号结点把森林连接成一棵树。

\(K\)级祖先转化为\(K\)级儿子,再转化成和深度有关的问题。

统计子树中深度为\(i\)的点即可。

注意:可能一个节点的\(K\)级祖先并不存在,所以在最开始要判断,否则会要统计深度为负数的点的个数。

Code

inline void dfs1(int st,int fa){
	sz[st]=1,dep[st]=dep[fa]+1;
	for(int i=1;i<=t;i++) f[st][i]=f[f[st][i-1]][i-1];
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==fa) continue;
		dfs1(ed,st);
		sz[st]+=sz[ed];
		if(sz[ed]>sz[son[st]]) son[st]=ed;
	}
	return;
}
inline void add_val(int st,int ban){
	++cnt[dep[st]];//记录深度为dep的点的出现次数
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==ban) continue;
		add_val(ed,ban);
	}
	return;
}
inline void del_val(int st){
	--cnt[dep[st]];
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		del_val(ed);
	}
	return;
}
inline void dfs2(int st){
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==son[st]) continue;
		dfs2(ed),del_val(ed);
	}
	if(son[st]) dfs2(son[st]);
	add_val(st,son[st]);
	for(int i=0;i<(int)qs[st].size();i++){
		int id=qs[st][i].first,k=qs[st][i].second;
		ans[id]=cnt[k+dep[st]]-1;//减去原先这个点本身
	}
    //qs里存的是与这个点有关的询问
	return;
}

CF1009F

Luogu

CF

Sol

依然是转化为和深度有关的问题。

和统计颜色一样的套路。

也可以把每个点的深度看成这个点的颜色。

Code

inline void dfs1(int st,int fa){
    sz[st]=1,dep[st]=dep[fa]+1,dfn[++tot]=st,L[st]=tot;
    for(int i=head[st];i;i=e[i].nx) {
        int ed=e[i].ed;
        if(ed==fa)  continue;
        dfs1(ed,st);
        sz[st]+=sz[ed];
        if(sz[ed]>sz[son[st]]) son[st]=ed;
    }
    R[st]=tot;
	return;
}
inline void ADD(int st){
	++cnt[dep[st]];
	if(cnt[dep[st]]>mx) mx=cnt[dep[st]],DEP=dep[st];
	else if(cnt[dep[st]]==mx) DEP=min(DEP,dep[st]);
	return;
}
inline void dfs2(int st,int fa){
	for(int i=head[st];i;i=e[i].nx){
		int ed=e[i].ed;
		if(ed==fa||ed==son[st]) continue;
		dfs2(ed,st);
		for(int j=L[ed];j<=R[ed];j++) --cnt[dep[dfn[j]]];
		mx=0,DEP=0;
	}
	if(son[st]) dfs2(son[st],st);
	for(int i=head[st];i;i=e[i].nx){
		int ed=e[i].ed;
		if(ed==fa||ed==son[st]) continue;
		for(int j=L[ed];j<=R[ed];j++) ADD(dfn[j]);
	}
	ADD(st);
	ans[st]=DEP-dep[st];
	return;
}

CF246E

Luogu

CF

Sol

依旧是和深度有关的问题。

这次节点有了两个属性,一个是深度,一个是名字,怎么办呢?

好办,对每一个深度开一个set,名字丢到set里就行。

Code

inline void dfs1(int st,int fa){
	sz[st]=1,dep[st]=dep[fa]+1;
	for(int i=1;i<=t;i++) f[st][i]=f[f[st][i-1]][i-1];
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==fa) continue;
		dfs1(ed,st);
		sz[st]+=sz[ed];
		if(sz[ed]>sz[son[st]]) son[st]=ed;
	}
	return;
}
inline void add_val(int st,int ban){
	++cnt[dep[st]];
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==ban) continue;
		add_val(ed,ban);
	}
	return;
}
inline void del_val(int st){
	--cnt[dep[st]];
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		del_val(ed);
	}
	return;
}
inline void dfs2(int st){
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==son[st]) continue;
		dfs2(ed),del_val(ed);
	}
	if(son[st]) dfs2(son[st]);
	add_val(st,son[st]);
	for(int i=0;i<(int)qs[st].size();i++){
		int id=qs[st][i].first,k=qs[st][i].second;
		ans[id]=cnt[k+dep[st]]-1;//cnt是一个set
	}
	return;
}

CF570D

Luogu

CF

Sol

异或每个深度所有节点的字母之后判断是否在二进制下最多只有1个1。

Code

inline int mpopcnt(ll x){
	int res=0;
	while(x) x-=(x&(-x)),++res;
	return res;
}
inline void dfs1(int st){
	sz[st]=1,dep[st]=dep[fa[st]]+1,dfn[++tot]=st,L[st]=tot;
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		dfs1(ed);
		sz[st]+=sz[ed];
		if(sz[ed]>sz[son[st]]) son[st]=ed;
	}
	R[st]=tot;
	return;
}
inline void dfs2(int st){
	for(int i=0;i<(int)e[st].size();i++){		
		int ed=e[st][i];
		if(ed==son[st]) continue;
		dfs2(ed);
		for(int j=L[ed];j<=R[ed];j++) cnt[dep[dfn[j]]]=0;
	}
	if(son[st]) dfs2(son[st]);
	cnt[dep[st]]^=(1<<(s[st-1]-'a'));
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==son[st]) continue;
		for(int j=L[ed];j<=R[ed];j++) cnt[dep[dfn[j]]]^=(1<<(s[dfn[j]-1]-'a'));
	}
	for(int i=0;i<(int)qs[st].size();i++){
		int id=qs[st][i].first,k=qs[st][i].second;
		ans[id]=(mpopcnt(cnt[k])<=1?1:0);
	}
	return;
}

The Grass Type

给你一棵树,求\(a_i\cdot a_j=a_{LCA(i,j)}\)的无序对\((i,j)\)的数量。

Sol

map存数的个数。

统计完儿子的答案再统计自己的答案。

如果是子树内有1,那能和根节点构成符合条件的无序对的数量为1的个数。

Code

inline void dfs1(int st,int f){
	sz[st]=1,dfn[++tot]=st,L[st]=tot,fa[st]=f;
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==fa[st]) continue;
		dfs1(ed,st);
		sz[st]+=sz[ed];
		if(sz[ed]>sz[son[st]]) son[st]=ed;
	}
	R[st]=tot;
	return;
}
inline void dfs2(int st){
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==fa[st]||ed==son[st]) continue;		
		dfs2(ed);
		mp.clear();
	}
	if(son[st]) dfs2(son[st]);
	for(int i=0;i<(int)e[st].size();i++){
		int ed=e[st][i];
		if(ed==fa[st]||ed==son[st]) continue;
		for(int j=L[ed];j<=R[ed];j++)
			if(w[st]%w[dfn[j]]==0) ans+=mp[w[st]/w[dfn[j]]];
		for(int j=L[ed];j<=R[ed];j++) ++mp[w[dfn[j]]];
	}
	ans+=mp[1];
	++mp[w[st]];
	return;
}

CF741D

填坑的题解

CF716E

CF291E

小trick

  • 询问一般用vector离线存在临时根节点处
  • 熟练使用set,map等STL容器能让你更快的解题
  • 枚举一般比递归快而且省事
  • 有什么想起来了再加

未完待续❀

posted @ 2021-09-14 20:15  xxbbkk  阅读(30)  评论(1编辑  收藏  举报