Dsu on tree

笑点解析:看了几篇博客没看懂,看一次 b 站看懂了。

前置:重链剖分。

Dsu on tree 树上启发式合并,其实就是树上优美暴力的一种,并不是某一种具体的算法,而更像是一种思想,通过把轻儿子信息合并到重儿子上,虽然是暴力,但是复杂度还是优秀的 O(nlogn),用于处理某些离线不带修改的子树信息。

具体的实现流程:

  1. 统计某点轻儿子的信息,注意我们是要保留每个节点的答案,所以此处也是统计轻儿子的全部答案,但是没有统计到当前节点来,也就是清除了轻儿子此时的统计数组。因为不清除的话轻儿子的答案会互相影响。

  2. 统计重儿子答案,保留统计数组。

  3. 再一遍统计轻儿子信息,每次都不清除信息,并算上该点合并答案,也就是这个点的答案。

感觉说的差不多了,一开始不懂的地方就是 1,3 步为什么不能合并,然后发现是在统计子树内的每个答案。

关于复杂度,这样做的复杂度是 O(nlogn) 的,我们每一次这样做相当于把一条轻边连接的子树遍历一遍,而一个点到根的轻边数量不会超过 O(logn),所以复杂度是 O(nlogn)

例题:CF600E

给定一棵树,每个点有颜色 ci,求每个子树内数量最多的颜色的编号和,数量最多的颜色可能会有多个。

n105ci105

这题可以线段树合并,莫队,但还是讲讲 dsu on tree 的做法,首先这是一个离线没有修改的子树问题,可以 dsu on tree,步骤大概和上述一致,结合代码来看。

我们要支持删除某一个子树的信息,所以在遍利的过程中需要加入判别变量。

inline void dfs(ll x,ll fa,ll opt)
{
	// if(!son[x]) return cnt[x]=c[x],void();
	for(ll y:g[x]){
		if(y==fa||y==son[x]) continue;
		dfs(y,x,0);
	}
	if(son[x]) dfs(son[x],x,1),flag=son[x];
	upd(x,fa,1);flag=0;cnt[x]=sum;
	if(!opt) upd(x,fa,-1),sum=maxn=0;
}

其中,第一部分循环就是进行轻儿子答案的统计,并且不保留答案,所以整个 dfs 函数可以看做是对某点及其子树整个的答案统计。然后再对重儿子进行操作,并且不清除数组。

inline void upd(ll x,ll fa,ll opt)
{
	vis[c[x]]+=opt;
	if(vis[c[x]]>maxn) maxn=vis[c[x]],sum=c[x];
	else if(vis[c[x]]==maxn) sum+=c[x];
	for(ll y:g[x])
		if(y!=fa&&y!=flag) upd(y,x,opt);
}

upd 函数中就是对答案的具体统计了,注意这个函数的意义是对该点及其轻子树的答案统计,这也就能解释 dfs 函数中再统计重儿子信息后的 upd 函数了,其实是为了再遍利轻儿子统计该点的总答案,最后是清除统计数组。

这样子,总的复杂度是 O(nlogn),可以通过该题。

Submisson

P3605 [USACO17JAN] Promotion Counting P

模版题,求子树内 aj>ai 的值的个数,树状数组维护即可,获得了较劣的 O(nlog2n) 做法,线段树合并可以做到大常数 O(nlogn)

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define inf 0x3f3f3f3f
#define pr putchar('\n')
#define fi first
#define se second
#define pp putchar(' ')
#define pii pair<ll,ll>
#define pdi pair<ll,ll>
#define mem(aa,bb) memset(aa,bb,sizeof(aa))
#define fo(a,i,b) for(register ll i = a ; i <= b ; ++ i )
#define Fo(a,i,b) for(register ll i = a ; i >= b ; -- i )
#define pb push_back
//#pragma GCC optimize(2)
using namespace std;
typedef int ll;
// typedef long long ll;
//typedef __int128 ll;
typedef double db;
inline void read(ll &opp){ll x=0,t=1;char ch;ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-'){t=-1;}ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}opp=x*t;return; }
inline void wr(ll x){if(x<0){putchar('-');x=-x;}if(x>9){wr(x/10);}putchar(x%10+'0');}
const ll N=1e6+5,M=2e4+5,mod=1e9+7;
ll n,p[N],x,a[N],ans[N],bit[N],siz[N],son[N],flag;
vector<ll> g[N];
#define lowbit(x) (x&(-x))
inline void upd(ll i,ll k){for(;i<=n;i+=lowbit(i)) bit[i]+=k;}
inline ll ask(ll i){ll ans=0;for(;i;i-=lowbit(i)) ans+=bit[i];return ans;}
inline void dfs1(ll x,ll fa){siz[x]=1;for(ll y:g[x]){if(y==fa) continue;dfs1(y,x);siz[x]+=siz[y];if(siz[y]>siz[son[x]]) son[x]=y;}}
inline void update(ll x,ll fa,ll opt)
{
	upd(p[x],opt);
	for(ll y:g[x]) if(y!=fa&&y!=flag) update(y,x,opt); 
}
inline void dfs(ll x,ll fa,ll opt)
{
	for(ll y:g[x])
	{
		if(y==fa||y==son[x]) continue;
		dfs(y,x,0);
	}
	if(son[x]) dfs(son[x],x,1),flag=son[x];
	update(x,fa,1);flag=0;ans[x]=ask(n)-ask(p[x]);
	if(!opt) update(x,fa,-1); 
}
signed main(){
	read(n);fo(1,i,n) read(p[i]),a[i]=p[i];sort(a+1,a+1+n);ll m=unique(a+1,a+1+n)-a-1;fo(1,i,n) p[i]=lower_bound(a+1,a+1+m,p[i])-a;
	fo(2,i,n) read(x),g[x].pb(i),g[i].pb(x);
	dfs1(1,0);dfs(1,0,0);fo(1,i,n) wr(ans[i]),pr;
    return 0;
}

CF570D

每个节点有 az 中的一个字母,每次查询以 a 为根的子树内深度为 b 的节点重新排列能否构成回文串。

模版,能够成回文串的条件就是奇数个数的字母个数不超过 1,树上启发式合并就做完了,注意需要开数组记录每个字母在每个深度下的奇偶性,这题卡的还是挺严的。

复杂度 O(nlogn)

Submission

CF1709E

感觉这个严格大于上一个题吧,每个点有权值,问至少多少次修改单点点权能使得不存在简单路径的异或和为 0

考虑枚举 lca,因为我们可以任意修改某点点值,所以我们一定可以让一条路径的异或和变大,随便取一个大数就好了。这样来看,我们只要修改 lca,就能使得他的子树内所有通过他的简单路径全部不为 0 对吧,假设他的儿子也都被修改过,也就是子树内不存在简单路径为 0,那我们就可以删去这一整个子树了。换一个角度想一下,我们如果从下往上判断,只要这个点为 lca,的路径出现了异或和为 0 的,那么我们就修改这个点的点权,否则不动,那么每一个修改点的子树是一定可以删去的,因为他的儿子要么被删去,要么符合条件。这样,我们只需要对每一个点维护一个 set,记录子树内没有被删除的点,假如这个点被修改,那么清空 set 即可。判别路径异或和,就等于 disidisjau=0,等价于寻找 disiau=disj,在 set 中查找即可,在合并 set 使用启发式合并就可以做到 O(nlog2n)

这题就是严格大于上一个吧,这题只是蓝吗。

Submission

posted @   Wei_Han  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示