树上启发式合并/dsu on tree
什么是树上启发式合并会依据第一道题目进行简单讲解。
题目1 洛谷-树上数颜色
题目大意
给我们一棵树,每个点有一个颜色,问我们每棵子树中的颜色种类数。
分析
考虑暴力,对于每个点我们都维护一个数组,表示该点表示的子树中的颜色种类,计算每个点的答案时,遍历整棵子树。时间复杂度为
n
2
n^2
n2,那怎么才能优化呢。
其实对于一个点,在计算完自己的答案后,他的数组就已经没用了,那我们是不是可以在所有儿子计算完答案后,将一个儿子的数组拿过来,然后将其他儿子的数组一一合并过来呢,当然是可以的。那一开始拿哪个儿子的数组最好呢,答案是他的重儿子。
所以我们首先将树轻重链剖分找出重儿子,维护一个全局的数组表示当前正在计算的点的信息。
先计算所有轻儿子的答案,每次计算完后都清空数组,最后计算重儿子的答案,计算完后不清空数组,而是暴力地将轻儿子的信息合并到重儿子上,最后作为这个点的答案。
这样做的时间复杂度是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10,M = 2e5+10;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int n,m;
int a[N];
int nowAns;
int cnt[N],ans[N];
int son[N],sz[N];
void dfs1(int u,int fa){
sz[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa)continue;
dfs1(j,u);
if(sz[son[u]]<sz[j])son[u]=j;
sz[u]+=sz[j];
}
}
void go(int u,int fa,int f){
cnt[a[u]]+=f;
if(f==1&&cnt[a[u]]==1)nowAns++;
if(f==-1&&cnt[a[u]]==0)nowAns--;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa)continue;
go(j,u,f);
}
}
void dfs2(int u,int fa,bool del){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa||j==son[u])continue;
dfs2(j,u,true);
}
if(son[u])dfs2(son[u],u,false);
cnt[a[u]]++;
if(cnt[a[u]]==1)nowAns++;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa||j==son[u])continue;
go(j,u,1);
}
ans[u]=nowAns;
if(del)go(u,fa,-1);
}
int main(){
memset(h,-1,sizeof h);
scanf("%d",&n);
for(int i=1;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b),add(b,a);
}
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
dfs1(1,-1);
dfs2(1,-1,false);
scanf("%d",&m);
while(m--){
int x;
scanf("%d",&x);
printf("%d\n",ans[x]);
}
return 0;
}
题目2 CF 600E
题目大意
给我们一棵树,每棵树都有一个颜色编号,对于一棵子树,内部有很多个颜色,最多的颜色则占据主导地位,当然也可能有多种颜色占据主导地位,要我们求每棵子树占主导地位的颜色编号之和。
分析
与上一道题类似,我们只需要在计算答案时维护一个当前最大值
m
x
mx
mx表示当前子树内每种颜色的个数的最大值,动态地更新这个值即可。
即若遍历到一个点时,该点代表颜色出现的次数
c
n
t
[
a
[
u
]
]
cnt[a[u]]
cnt[a[u]]大于
m
x
mx
mx,我们就将
c
n
t
[
a
[
u
]
]
cnt[a[u]]
cnt[a[u]]赋给
m
x
mx
mx,同时将
a
[
u
]
a[u]
a[u]赋给
n
o
w
A
n
s
nowAns
nowAns;若遍历到一个点时,该点代表颜色出现的次数
c
n
t
[
a
[
u
]
]
cnt[a[u]]
cnt[a[u]]等于
m
x
mx
mx,我们就将
a
[
u
]
a[u]
a[u]加到
n
o
w
A
n
s
nowAns
nowAns上。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10,M = 2e5+10;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int n,m;
int a[N];
int nowAns;
int cnt[N],ans[N];
int son[N],sz[N];
int mx;
void dfs1(int u,int fa){
sz[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa)continue;
dfs1(j,u);
if(sz[son[u]]<sz[j])son[u]=j;
sz[u]+=sz[j];
}
}
void go(int u,int fa,int f){
cnt[a[u]]+=f;
if(f==1){
if(cnt[a[u]]>mx){
mx=cnt[a[u]];
nowAns=a[u];
}else if(cnt[a[u]]==mx){
nowAns+=a[u];
}
}
else{
mx=0;
nowAns=0;
}
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa)continue;
go(j,u,f);
}
}
void dfs2(int u,int fa,bool del){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa||j==son[u])continue;
dfs2(j,u,true);
}
if(son[u])dfs2(son[u],u,false);
if(cnt[a[u]]==1)nowAns++;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa||j==son[u])continue;
go(j,u,1);
}
cnt[a[u]]++;
if(cnt[a[u]]>mx){
mx=cnt[a[u]];
nowAns=a[u];
}else if(cnt[a[u]]==mx){
nowAns+=a[u];
}
ans[u]=nowAns;
if(del)go(u,fa,-1);
}
int main(){
memset(h,-1,sizeof h);
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b),add(b,a);
}
dfs1(1,-1);
dfs2(1,-1,false);
for(int i=1;i<=n;i++)printf("%d ",ans[i]);
return 0;
}
题目3 赛氪 树的果实
题目大意
给我们一棵树,求每棵子树中 ( 每个数字出现的次数 ∗ 该数字 ) 2 (每个数字出现的次数*该数字)^2 (每个数字出现的次数∗该数字)2的和。
分析
如果这个题没有平方,那我们遇到一个数 a [ u ] a[u] a[u]就向 n o w A n s nowAns nowAns加一个 a [ u ] a[u] a[u]即可,但是有了评分我们应该怎么做呢,其实很简单,我们先减掉这个数之前的贡献,再加上 ( a [ u ] ∗ c n t [ a [ u ] ] ) 2 (a[u]*cnt[a[u]])^2 (a[u]∗cnt[a[u]])2即可。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5+10,M = 2e5+10;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int n,m,c;
ll a[N];
ll nowAns;
ll cnt[N],ans[N];
int son[N],sz[N];
void dfs1(int u){
sz[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
dfs1(j);
if(sz[son[u]]<sz[j])son[u]=j;
sz[u]+=sz[j];
}
}
void go(int u,int f){
cnt[a[u]]+=f;
if(f==1){
int t=cnt[a[u]]-1;
nowAns-=a[u]*t*a[u]*t;
nowAns+=a[u]*cnt[a[u]]*a[u]*cnt[a[u]];
}
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
go(j,f);
}
}
void dfs2(int u,bool del){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==son[u])continue;
dfs2(j,true);
}
if(son[u])dfs2(son[u],false);
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==son[u])continue;
go(j,1);
}
ans[u]=nowAns;
cnt[a[u]]++;
int t=cnt[a[u]]-1;
nowAns-=a[u]*t*a[u]*t;
nowAns+=a[u]*cnt[a[u]]*a[u]*cnt[a[u]];
if(del)go(u,-1),nowAns=0;
}
int main(){
memset(h,-1,sizeof h);
scanf("%d%d%d",&n,&m,&c);
for(int i=1;i<n;i++){
int u,v;
ll x;
scanf("%d%d%lld",&u,&v,&x);
add(u,v);
a[v]=x;
}
dfs1(1);
dfs2(1,false);
while(m--){
int x;
scanf("%d",&x);
printf("%lld\n",ans[x]);
}
return 0;
}
题目4 CF 1709E XOR树
题目大意
给我们一棵树,每个点有一个点权,每条路径的价值为这条路径上所有点的异或和,若一棵树是好的需要满足所有路径的价值均不为0,我们可以修改任意一个点的点权,问我们最少修改几次可以使得一棵树变为好的。
分析
参考下面这位大佬的
Educational Codeforces Round 132 (Rated for Div. 2) A - E
我的代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5+10,M = 4e5+10;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int n;
int a[N];
int ans;
set<int>S;
vector<int>vec;
int f[N];
int tag,success,current;
int son[N],sz[N];
void dfs1(int u,int fa){
sz[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa)continue;
dfs1(j,u);
if(sz[son[u]]<sz[j])son[u]=j;
sz[u]+=sz[j];
}
}
void go(int u,int fa,int sum){
if(f[u]||success)return;
sum^=a[u];
vec.push_back(sum);
if(S.count(sum^current^tag)){
success=1;
return;
}
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa)continue;
go(j,u,sum);
}
}
void dfs2(int u,int fa,bool del){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==son[u]||j==fa)continue;
dfs2(j,u,true);
}
if(son[u])dfs2(son[u],u,false);
S.insert(tag);
success=0;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==son[u]||j==fa)continue;
vec.clear();
current=a[u];
go(j,u,0);
for(auto x:vec){
S.insert(x^tag);
}
}
if(S.count(a[u]^tag)){
success=1;
}
if(success){
f[u]=1;
tag=0;
S.clear();
}else if(del){
tag=0;
S.clear();
}else{
tag^=a[u];
}
}
int main(){
memset(h,-1,sizeof h);
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
dfs1(1,-1);
dfs2(1,-1,false);
for(int i=1;i<=n;i++)ans+=f[i];
printf("%d\n",ans);
return 0;
}