Dsu on Tree
这个属于一种技巧,可以解决类似于子树询问无修改可离线的问题,一些点分治的问题也可以用Dsu on Tree解决,并且常数较小,代码复杂度低,很具有可写性。
整体上的意思就是继承重儿子的信息,暴力修改轻儿子的信息,时间复杂度的证明类似并查集的启发式合并(本质上这个就是启发式合并)。
通常情况下,题目长成询问某种东西的数量,或者某种点对的数量。
例题时间
Educational Codeforces Round 2 E Lomsat gelral
$n$个点的有根树,以$1$为根,每个点有一种颜色。我们称一种颜色占领了一个子树当且仅当没有其他颜色在这个子树中出现得比它多。求占领每个子树的所有颜色之和。
莫队可以解决这个问题,想写莫队的可以叉掉这个网页然后去AC了。
那么我们考虑用Dsu on Tree解决这个问题。
我们要做的是为何一个和,和一个最大值还有一个桶,那么,把对应的维护出来,然后暴力更新即可。
你说不会?暴力会不会?就是暴力啊…如果还不会那么就看代码吧…
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <queue> #include <iostream> using namespace std; #define N 100005 struct node{int to,next;}e[N<<1]; int head[N],val[N],col[N],maxx,siz[N],son[N],n,cnt;long long ans[N],sum; void add(int x,int y){e[cnt]=(node){y,head[x]};head[x]=cnt++;} void dfs1(int x,int from) { siz[x]=1; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from)dfs1(to1,x),siz[x]+=siz[to1],son[x]=(siz[to1]>siz[son[x]]?to1:son[x]); } } void add(int x,int from,int c) { col[val[x]]+=c; if(col[val[x]]>=maxx&&c>0) { if(col[val[x]]>maxx)maxx=col[val[x]],sum=0; sum+=val[x]; } for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from)add(to1,x,c); } } void dfs2(int x,int from,int op) { for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&to1!=son[x])dfs2(to1,x,0); }if(son[x])dfs2(son[x],x,1); for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&to1!=son[x])add(to1,x,1); } col[val[x]]++; if(col[val[x]]>=maxx) { if(col[val[x]]>maxx)maxx=col[val[x]],sum=0; sum+=val[x]; } ans[x]=sum; if(!op)add(x,from,-1),sum=0,maxx=0; } int main() { scanf("%d",&n);memset(head,-1,sizeof(head)); for(int i=1;i<=n;i++)scanf("%d",&val[i]); for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x); dfs1(1,0);dfs2(1,0,1); for(int i=1;i<=n;i++)printf("%lld ",ans[i]);return 0; }
Codeforces Round #383 (Div. 1) D
我们称一个字符串为doubi string当且仅当重排它的字符可以组成一个回文串。
给出一个$n$个点的有根树,根为$1$,每条边上有一个字符(只有$a \sim v$,别问我为什么),求每个点的子树中所有简单路径可以组成的doubi string中的最长长度。
这个题其实求的就是树上的一条最大只有一个字母出现了奇数次的最长链。
然后维护一下$s_i$表示$i$到根的字符状态,然后每次将轻重儿子信息合并的时候更新答案。
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <queue> #include <iostream> using namespace std; #define N 500005 struct node{int to,next;}e[N<<1]; int head[N],siz[N],cnt,n,S[1<<23],val[N],son[N],dep[N],ans[N],tmp_ans; void add(int x,int y){e[cnt]=(node){y,head[x]};head[x]=cnt++;} void dfs1(int x,int from) { dep[x]=dep[from]+1,siz[x]=1,val[x]^=val[from]; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from)dfs1(to1,x),siz[x]+=siz[to1],son[x]=(siz[to1]>siz[son[x]]?to1:son[x]); } } #define change(x) S[val[x]]=max(dep[x],S[val[x]]); void add(int x,int from,bool op) { if(op)S[val[x]]=max(dep[x],S[val[x]]); else S[val[x]]=-0x3f3f3f3f; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from)add(to1,x,op); } } void get(int x) { tmp_ans=max(tmp_ans,S[val[x]]+dep[x]); for(int i=0;i<=22;i++)tmp_ans=max(tmp_ans,S[val[x]^(1<<i)]+dep[x]); } void get_ans(int x,int from) { get(x); for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from)get_ans(to1,x); } } void dfs2(int x,int from,int op) { for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&to1!=son[x])dfs2(to1,x,0); }if(son[x])dfs2(son[x],x,1),ans[x]=ans[son[x]]; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&to1!=son[x]) get_ans(to1,x),ans[x]=max(ans[x],ans[to1]),add(to1,x,1); }get(x);change(x); // printf("%d\n",x); // for(int i=1;i<(1<<23);i++)if(S[i]>0)printf("%d ",i);puts(""); ans[x]=max(ans[x],tmp_ans-(dep[x]<<1)),tmp_ans=0; if(!op)add(x,from,0); }char rr[2]; int main() { scanf("%d",&n);memset(head,-1,sizeof(head));memset(S,-0x3f,sizeof(S)); for(int i=2,x;i<=n;i++)scanf("%d%s",&x,rr),val[i]=1<<(rr[0]-'a'),add(x,i),add(i,x); dfs1(1,0);dfs2(1,0,1);for(int i=1;i<=n;i++)printf("%d ",ans[i]);return 0; }