zoukankan      html  css  js  c++  java
  • 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_icdot 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容器能让你更快的解题
    • 枚举一般比递归快而且省事
    • 有什么想起来了再加

    未完待续❀

  • 相关阅读:
    微信小程序封装get、post请求
    自定义原生jsonp跨域请求
    cookie的使用以及cookie的跨域名获取
    split,splice,slice,substr,substring使用方法总结
    树莓派吃灰
    树莓派吃灰
    树莓派吃灰
    Modbus读取MF5700系列流量计
    .net mvc ajaxfileupload.js 上传文件
    根据字段名查找数据库中包含的所有表
  • 原文地址:https://www.cnblogs.com/xxbbkk/p/15269407.html
Copyright © 2011-2022 走看看