树上启发式合并学习笔记
树上启发式合并
适用条件:
可以在一个子树内统计的问题,并且不带修改。暴力复杂度一般为
例题:
CF600E Lomsat gelral
解法
考虑一个问题 ,给你一棵树,每个节点有一个颜色,如果一种颜色在以
我们考虑 dsu on tree
。首先维护出每个节点的重儿子 dfs
函数求答案。
首先对于
复杂度为
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+7;
int n,col[N];
vector<int> G[N];
int sz[N],dep[N],son[N];
int num[N],sum,maxN;
int ans[N];
void dfs(int u,int father){
sz[u]=1;
for(int k:G[u]){
if(k==father) continue;
dfs(k,u);sz[u]+=sz[k];
if(!son[u]||sz[k]>sz[son[u]]) son[u]=k;
}
}
void updata(int u,int father,int son,int val){
num[col[u]]+=val;
if(num[col[u]]>maxN) maxN=num[col[u]],sum=col[u];
else if(num[col[u]]==maxN) sum+=col[u];
for(int k:G[u]){
if(k!=father&&k!=son) updata(k,u,son,val);
}
}
void DFS(int u,int father,bool kep){
for(int k:G[u]){
if(k!=father&&k!=son[u]) DFS(k,u,0);
}
if(son[u]) DFS(son[u],u,1);
updata(u,father,son[u],1);
ans[u]=sum;
if(!kep) updata(u,father,-1,-1),sum=maxN=0;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&col[i]);
for(int i=1;i<n;i++){
int u,v;scanf("%lld%lld",&u,&v);G[u].push_back(v),G[v].push_back(u);
}
dfs(1,-1);DFS(1,-1,1);
for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
return 0;
}
CF208E Blood Cousins
解法
对于一棵树,或者一座森林,询问
我们考虑 dsu on tree
。首先我们从 dsu on tree
思路同上一题。
注意:
- 我们建一个虚拟根
号点便于我们进行 dfs,但是我们在判断是否有解的情况时不能把 当作 的 级祖先,因而我们判断时需要满足dep[x]>=+1
才有解,反之无解。 - 注意计算贡献或删除贡献时不要重复计算或删除。
- 因为对于每个子树,
也被计算过一次,因而最后答案要减一。
复杂度
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
int n;
vector<int> G[N];
vector<pair<int,int> > Q[N];
int sz[N],dep[N],son[N];
int cnt[N];
int ans[N];
int f[100000][30];
int q;
void dfs(int u,int father){
f[u][0]=father,dep[u]=dep[father]+1;
sz[u]=1;
for(int k:G[u]){
if(k==father) continue;
dfs(k,u);sz[u]+=sz[k];
if(!son[u]||sz[k]>sz[son[u]]) son[u]=k;
}
}
void updata(int u,int father,int son,int val){
cnt[dep[u]]+=val;
for(int k:G[u]){
if(k!=father&&k!=son) updata(k,u,son,val);
}
}
void DFS(int u,int father,bool kep){
for(int k:G[u]){
if(k!=father&&k!=son[u]) DFS(k,u,0);
}
if(son[u]) DFS(son[u],u,1);
updata(u,father,son[u],1);
for(auto it:Q[u]){
int _dep=it.first,_id=it.second;
ans[_id]=cnt[_dep];
}
if(!kep) updata(u,father,-1,-1);
}
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int fa;scanf("%d",&fa);
G[fa].push_back(i);
}
dfs(0,-1);
for(int j=1;j<=20;j++){
for(int i=1;i<=n;i++){
f[i][j]=f[f[i][j-1]][j-1];
}
}
scanf("%d",&q);
for(int i=1;i<=q;i++){
int x,d;
scanf("%d%d",&x,&d);
if(dep[x]<=d+1) continue;
for(int k=0;k<=20;k++){
if((1<<k)&d) x=f[x][k];
}
Q[x].push_back(make_pair(dep[x]+d,i));//亲戚的深度和问题编号。
}
DFS(0,-1,-1);
for(int i=1;i<=q;i++) printf("%d ",ans[i]==0?0:ans[i]-1);
return 0;
}
CF1009F Dominant Indices
解法
对于一个以
我们考虑 dsu on tree
。维护
复杂度
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e6+7;
int n,col[N];
vector<int> G[N];
int sz[N],dep[N],son[N];
int num[N],sum,maxN;
int ans[N];
int Dis;
void dfs(int u,int father){
sz[u]=1;dep[u]=dep[father]+1;
for(int k:G[u]){
if(k==father) continue;
dfs(k,u);sz[u]+=sz[k];
if(!son[u]||sz[k]>sz[son[u]]) son[u]=k;
}
}
void updata(int u,int father,int son,int val){
num[dep[u]]+=val;
if(num[dep[u]]>maxN||((maxN==num[dep[u]])&&(dep[u]<Dis))) Dis=dep[u],maxN=num[dep[u]];
for(int k:G[u]){
if(k!=father&&k!=son) updata(k,u,son,val);
}
}
void DFS(int u,int father,bool kep){
for(int k:G[u]){
if(k!=father&&k!=son[u]) DFS(k,u,0);
}
if(son[u]) DFS(son[u],u,1);
updata(u,father,son[u],1);
ans[u]=Dis-dep[u];
if(!kep) updata(u,father,-1,-1),Dis=0,maxN=0;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<n;i++){
int u,v;scanf("%lld%lld",&u,&v);G[u].push_back(v),G[v].push_back(u);
}
dfs(1,0);DFS(1,0,1);
// for(int i=1;i<=n;i++) printf("%d ",dep[i]);puts("");
for(int i=1;i<=n;i++) printf("%lld\n",ans[i]);
return 0;
}
CF375D Tree and Queries
解法
给定一棵树,每个节点上有一个颜色。
我们考虑 dsu on tree
。对于每个颜色维护一个出现次数
注意这道题 updata
不能写在一起,因为加的时候我们是该颜色出现次数 +1,更改后的达标次数 +1,而减的时候需要先把达标次数清空或 -1,再把该颜色出现次数 -1。这样避免了清错的情况发生。
复杂度
代码1
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e6+7;
int n,col[N];
vector<int> G[N];
int sz[N],dep[N],son[N];
int num[N],sum,maxN;
int ans[N];
int Dis;
int m;
vector<pair<int,int> > Q[N];
int times[N];
void dfs(int u,int father){
sz[u]=1;dep[u]=dep[father]+1;
for(int k:G[u]){
if(k==father) continue;
dfs(k,u);sz[u]+=sz[k];
if(!son[u]||sz[k]>sz[son[u]]) son[u]=k;
}
}
void updata(int u,int father,int son,int val){
num[col[u]]+=val;
times[num[col[u]]]+=val;
for(int k:G[u]){
if(k!=father&&k!=son) updata(k,u,son,val);
}
}
void clear(int u,int father,int son,int val){
times[num[col[u]]]=0;
num[col[u]]--;
for(int k:G[u]){
if(k!=father&&k!=son) clear(k,u,son,val);
}
}
void DFS(int u,int father,bool kep){
for(int k:G[u]){
if(k!=father&&k!=son[u]) DFS(k,u,0);
}
if(son[u]) DFS(son[u],u,1);
updata(u,father,son[u],1);
for(auto it:Q[u]){
int k=it.first,id=it.second;
ans[id]=times[k];
}
if(!kep) clear(u,father,-1,-1);
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&col[i]);
for(int i=1;i<n;i++){
int u,v;scanf("%lld%lld",&u,&v);G[u].push_back(v),G[v].push_back(u);
}
for(int i=1;i<=m;i++){
int x,k;
scanf("%lld%lld",&x,&k);
Q[x].push_back(make_pair(k,i));
}
dfs(1,0);DFS(1,0,1);
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
return 0;
}
代码2
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e6+7;
int n,col[N];
vector<int> G[N];
int sz[N],dep[N],son[N];
int num[N],sum,maxN;
int ans[N];
int times[N];
int Dis;
int m;
vector<pair<int,int> > Q[N];
int dfn_clock;
int dfn[N];
int pos[N];
void dfs(int u,int father){
sz[u]=1;dep[u]=dep[father]+1;
dfn[u]=++dfn_clock;
pos[dfn_clock]=u;
for(int k:G[u]){
if(k==father) continue;
dfs(k,u);sz[u]+=sz[k];
if(!son[u]||sz[k]>sz[son[u]]) son[u]=k;
}
}
void add(int x){
for(int i=dfn[x];i<=dfn[x]+sz[x]-1;i++){
num[col[pos[i]]]++;
times[num[col[pos[i]]]]++;
}
}
void del(int x){
for(int i=dfn[x];i<=dfn[x]+sz[x]-1;i++){
times[num[col[pos[i]]]]=0;
num[col[pos[i]]]--;
}
}
void DFS(int u,int father,bool kep){
for(int k:G[u]){
if(k!=father&&k!=son[u]) DFS(k,u,0);
}
if(son[u]) DFS(son[u],u,1);
for(int k:G[u]){
if(k!=father&&k!=son[u]) add(k);
}
num[col[u]]++;
times[num[col[u]]]++;
// printf("%lld ",times[num[col[u]]]);
for(auto it:Q[u]){
int k=it.first,id=it.second;
ans[id]=times[k];
}
if(!kep) del(u);
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&col[i]);
for(int i=1;i<n;i++){
int u,v;scanf("%lld%lld",&u,&v);G[u].push_back(v),G[v].push_back(u);
}
for(int i=1;i<=m;i++){
int x,k;
scanf("%lld%lld",&x,&k);
Q[x].push_back(make_pair(k,i));
}
dfs(1,0);DFS(1,0,1);
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
return 0;
}
CF246E Blood Cousins Return
解法
给定一片森林,询问编号为
考虑 dsu on tree
的做法。用 set 维护字符串出现次数。注意读入输出格式。同时注意本题是森林,需要多次 dfs 并且每次 dfs 起始点都不同,都是当前状态下没有
复杂度
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+7;
int n,col[N];
vector<int> G[N];
int sz[N],dep[N],son[N];
int num[N],sum,maxN;
int ans[N];
int fa[N];
int Dis;
int m;
vector<pair<int,int> > Q[N];
int times[N];
string a[N];
set<string> s[N];
void dfs(int u,int father){
sz[u]=1;dep[u]=dep[father]+1;
for(int k:G[u]){
if(k==father) continue;
dfs(k,u);sz[u]+=sz[k];
if(!son[u]||sz[k]>sz[son[u]]) son[u]=k;
}
}
void updata(int u,int father,int son,int val){
s[dep[u]].insert(a[u]);
for(int k:G[u]){
if(k!=father&&k!=son) updata(k,u,son,val);
}
}
void clear(int u,int father,int son,int val){
s[dep[u]].clear();
for(int k:G[u]){
if(k!=father&&k!=son) clear(k,u,son,val);
}
}
void DFS(int u,int father,bool kep){
for(int k:G[u]){
if(k!=father&&k!=son[u]) DFS(k,u,0);
}
if(son[u]) DFS(son[u],u,1);
updata(u,father,son[u],1);
for(auto it:Q[u]){
int k=it.first,id=it.second;
ans[id]=s[k].size();
}
if(!kep) clear(u,father,-1,-1);
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
int u;
cin>>a[i]>>u;
G[u].push_back(i);
fa[i]=u;
}
for(int i=1;i<=n;i++){
if(!fa[i]) dfs(i,0);
}
scanf("%lld",&m);
for(int i=1;i<=m;i++){
int x,k;
scanf("%lld%lld",&x,&k);
Q[x].push_back(make_pair(k+dep[x],i));
}
for(int i=1;i<=n;i++) if(!fa[i]) DFS(i,0,0);
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】