树上启发式合并 ~ dsu on tree

算法内容

主要解决:静态子树查询问题。

步骤

遍历一个节点 ,我们按以下的步骤进行遍历:

  1. 先遍历 u u u 的轻(非重)儿子,并计算答案,但 不保留遍历后它对 u u u影响;
  2. 遍历它的重儿子,保留它对 u u u 的影响;
  3. 再次遍历 u u u 的轻儿子的子树结点,加入这些结点的贡献,以得到 u u u 的答案。

主要代码

void get_data(int u){

	// 对于u的子树遍历问题 转换为的欧拉序 L[u] ~ R[u];
	for(int i = L[u];i<=R[u];i++){
		int t = index[i];
		
		//重儿子信息已经记录
		if(index[i] == skp){
			i = R[index[i]];
			continue;
		}	
		
		ans[u] = …………;
	
	}

}
void dsu(int u){
	
	//遍历轻儿子
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(y == fa[u] || y == son[u])continue;
		dsu(y);
	}
	
	//遍历重儿子
	if(son[u]){
		dsu(son[u]);
		skp = son[u];
	}
	
	//处理该节点的值
	get_data(u);

	//不为重儿子 清除.	
	if(u == top[u]){
		//clear();
		skp = 0;
	}
	
}

例题

例题1 树的果实 (赛氪)

树的果实 (赛氪)

例题2 DongDong数颜色

DongDong数颜色

统计子树不同的颜色个数

#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5+10,M = N*2;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
    ne[idx] = h[a],e[idx] = b,h[a] = idx++;
}
int n,m;
int a[N];
int col[N],ans[N];
int L[N],R[N],dfn,Index[N];
int top[N],son[N],siz[N],fa[N];
void dfs1(int u,int f){
    L[u] = ++dfn;
    Index[dfn] = u;
    fa[u] = f;
    siz[u] = 1;
    for(int i = h[u];~i;i=ne[i]){
        int y = e[i];
        if(y == f)continue;
        dfs1(y,u);
        siz[u] += siz[y];
        if(siz[son[u]] < siz[y])son[u] = y;
    }
    R[u] = dfn;
}
void dfs2(int v,int u){
    top[v] = u;
    if(!son[v])return ;
    dfs2(son[v],u);
    for(int i = h[v];~i;i=ne[i]){
        int y = e[i];
        if(y != fa[v] && y != son[v])dfs2(y,y);
    }
}
int skp,tot;
void get_data(int x,int op){
//	cout<< " x: "<< x << endl;
    for(int i = L[x];i<=R[x];i++){
        if(Index[i] == skp){
            i = R[Index[i]];
            continue;
        }
//       cout << Index[i] << " " << endl;
        if(op==1 && ++col[a[Index[i]]] == 1)tot ++;
        if(op==-1 && --col[a[Index[i]]] == 0)tot --;
    }
 //   cout << endl;
}
void dsu(int x){
 
    for(int i = h[x];~i;i=ne[i]){
        int y = e[i];
        if(y == son[x] || y == fa[x])continue;
        dsu(y);
    }
    
    if(son[x]){
    	dsu(son[x]);
        skp = son[x];
    }
    
    get_data(x,1);
    //cout << "x: " << x << "  "<< tot << endl;    
    ans[x] = tot;
    
    if(x == top[x]){
		skp = 0;
        get_data(x,-1);
    }
    
}
int main(){
    cin >> n >> m;
    for(int i = 1;i<=n;i++){
        cin >> a[i];
    }
    memset(h,-1,sizeof h);
    for(int i = 1;i<n;i++){
        int a,b;
        cin >> a >> b;
        add(a,b),add(b,a);
    }
    
    dfs1(1,0);
	dfs2(1,0);
    dsu(1);
    
    while(m--){
        int a;
        cin >> a;
        cout << ans[a] << endl;
    }
    return 0;
}

例题3 D. Tree Requests

D. Tree Requests

题意:

给你一颗树,顶点的权值为 a a a ~ z z z的字母,对于顶点 u u u找深度为 d d d的顶点能不能组成回文。 (这个深度为从根开始的.);

思路:

暴力维护 c n t [ d e p ] [ c h a r ] cnt[dep][char] cnt[dep][char] 深度为 d e p dep dep c h a r char char的个数.
对于是否能够组成回文:要么字母全为偶数,要么只有一个为奇数。(亦或运算)。

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N = 5e5+10,M = N*2;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
    ne[idx] = h[a],e[idx] = b,h[a] = idx++;
}
int n,m;
struct node{
	int id,dp;
};
vector<node> q[N];
int dep[N];
char c[N];
int col[N][30];
long long ans[N];
int L[N],R[N],dfn,Index[N];
int top[N],son[N],siz[N],fa[N];

void dfs1(int u,int f){
    L[u] = ++dfn;
    Index[dfn] = u;
    fa[u] = f;
    siz[u] = 1;
    dep[u] = dep[f] + 1;
	for(int i = h[u];~i;i=ne[i]){
        int y = e[i];
        if(y == f)continue;
        dfs1(y,u);
        siz[u] += siz[y];
        if(siz[son[u]] < siz[y])son[u] = y;
    }
    R[u] = dfn;
}
void dfs2(int v,int u){
    top[v] = u;
    if(!son[v])return ;
    dfs2(son[v],u);
    for(int i = h[v];~i;i=ne[i]){
        int y = e[i];
        if(y != fa[v] && y != son[v])dfs2(y,y);
    }
}
int skp,tot;
void add(int x){
	
	for(int i = L[x]+1;i<=R[x];i++){
		if(Index[i] == skp){
			col[dep[skp]][c[skp] - 'a'] ^= 1;
			i = R[Index[i]];
			continue;
		}
		//col(x,y) 深度为x,字母为y的个数 
		col[dep[Index[i]]][c[Index[i]] - 'a'] ^= 1;
	}
	
}

void sub(int x){
	for(int i = L[x];i<=R[x];i++){
		col[dep[Index[i]]][c[Index[i]] - 'a'] = 0;
	}
}

void dsu(int x){
 
    for(int i = h[x];~i;i=ne[i]){
        int y = e[i];
        if(y == son[x] || y == fa[x])continue;
        dsu(y);
    }
    
    if(son[x]){
    	dsu(son[x]);
        skp = son[x];
    }
    
    add(x);
   
   	for(auto s : q[x]){
   		int d = s.dp,id = s.id;
   		for(int i = 0;i<26;i++){
   		//	cout << char(i+'a') << col[d][i] << endl;
   			ans[id] += col[d][i];
		}
	} 
	
    if(x == top[x]){
		skp = 0;
    	sub(x);    
	}
    
}
int main(){
    cin >> n >> m;
   	memset(h,-1,sizeof h);
	for(int i = 2,a;i<=n;i++){
   		scanf("%d",&a);
   		add(i,a),add(a,i);
   	}
   	getchar();
    for(int i = 1;i<=n;i++){
        scanf("%c",&c[i]);
    }
    
   	for(int i = 1;i<=m;i++){
  		int u,d;
		scanf("%d%d",&u,&d);
		q[u].push_back({i,d});	
	} 

    dfs1(1,0);
    dep[0] = -1;
	dfs2(1,0);
    dsu(1);
	
	for(int i = 1;i<=m;i++){
		if(ans[i] <= 1)printf("Yes\n");
		else printf("No\n");
	}

    return 0;
}


例题:E. Blood Cousins Return

E. Blood Cousins Return

题目:
和上一题差不多。
题目给的是森林,每个顶点有个 n a m e name name,求对于顶点 u u u深度为 d d d的不同 n a m e name name的个数.
(这个 d d d是相对于 u u u)

思路:

建立一个原点 1 1 1,将森林变成一颗树。
对于深度 d d d可以加上 u u u的深度。
求不同name的个数和上一题一样了。

注意:

  1. + d e p +dep +dep时会越界(数组开到 2 e 5 2e5 2e5)
  2. 计数数组用map.

数组:
d p [ x ] : dp[x]: dp[x]深度为 x x x的不同 n a m e name name的个数。
c o l [ x ] [ y ] : col[x][y] : col[x][y]:深度为 x x x n a m e name name y y y的个数。

代码:

#include<iostream>
#include<cstring>
#include<vector>
#include<map>
using namespace std;
const int N = 2e5+10,M = N*2;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
   ne[idx] = h[a],e[idx] = b,h[a] = idx++;
}
int n,m;
struct node{
   int id,dp;
};
map<string,int> mp;
map<int,int> col[N];
vector<node> q[N];
int dep[N];
int c[N];
long long ans[N];
int L[N],R[N],dfn,Index[N];
int top[N],son[N],siz[N],fa[N];
int cnt;

void dfs1(int u,int f){
   L[u] = ++dfn;
   Index[dfn] = u;
   fa[u] = f;
   siz[u] = 1;
   dep[u] = dep[f] + 1;
   for(int i = h[u];~i;i=ne[i]){
       int y = e[i];
       if(y == f)continue;
       dfs1(y,u);
       siz[u] += siz[y];
       if(siz[son[u]] < siz[y])son[u] = y;
   }
   R[u] = dfn;
}
void dfs2(int v,int u){
   top[v] = u;
   if(!son[v])return ;
   dfs2(son[v],u);
   for(int i = h[v];~i;i=ne[i]){
       int y = e[i];
       if(y != fa[v] && y != son[v])dfs2(y,y);
   }
}
int skp,tot;
int dp[N];
void add(int x){
   
   for(int i = L[x]+1;i<=R[x];i++){
   //	cout << " x : " << x << " index : " << Index[i] << endl;
   	if(Index[i] == skp){
   		if( ++ col[dep[skp]][c[skp]] == 1) dp[dep[skp]] ++;
   	//	cout << dp[dep[Index[i]]] << endl;
   		i = R[Index[i]];
   		continue;
   	}
   	//col(x,y) : 表示深度为x,id为y的个数
   	//cout << "name : "<< c[Index[i]] << endl;
   //	cout << col[dep[Index[i]]][c[Index[i]]] << endl;
		if( ++ col[dep[Index[i]]][c[Index[i]]] == 1) dp[dep[Index[i]]] ++;
   	//cout << dp[dep[Index[i]]] << endl;
   }
   
}

void sub(int x){
   for(int i = L[x]+1;i<=R[x];i++){
   	if( -- col[dep[Index[i]]][c[Index[i]]] == 0) dp[dep[Index[i]]] --;
   	//cout << "sub L " << col[dep[Index[i]]][c[Index[i]]] << endl;
   }
}

void dsu(int x){

   for(int i = h[x];~i;i=ne[i]){
       int y = e[i];
       if(y == son[x] || y == fa[x])continue;
       dsu(y);
   }
   
   if(son[x]){
   	dsu(son[x]);
       skp = son[x];
   }
   
   add(x);
  
  	for(auto s : q[x]){
  		int d = s.dp,id = s.id;
  	//	cout << x << " " << d << " " << id  << endl;
  	//	cout << dp[d] << endl;
  		ans[id] = dp[d];
   } 
   
   if(x == top[x]){
   	skp = 0;
   //	cout << "insub : " << x << endl;
   	sub(x);    
   }
   
}
int main(){
   cin >> n ;
  	memset(h,-1,sizeof h);
   for(int i = 2;i<=n+1;i++){
   	string name;
   	int f;
   	cin >> name >> f;
   	if(!mp[name])mp[name] = ++cnt;
   	c[i] = mp[name];
   	if(f == 0)add(1,i),add(i,1);
  		else add(i,f+1),add(f+1,i);
  	}
   
  	dfs1(1,0);
   dep[0] = -1;
   dfs2(1,0);
   
   cin >> m;
   for(int i = 1;i<=m;i++){
 		int u,d;
   	scanf("%d%d",&u,&d);
   	u++;
   	q[u].push_back({i,d+dep[u]});	
   } 

   dsu(1);
   
   for(int i = 1;i<=m;i++){
   	cout << ans[i] << endl;
   }

   return 0;
}

例题 E. Blood Cousins

E. Blood Cousins

题意:
求第 u u u个节点的第 d d d层表亲个数,表亲定义:如果 u u u, v v v的向上 d d d层是 z z z,则 u , v u,v u,v互为表亲。

思路:
u u u d d d级祖先,就转换为上个题了。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
const int N = 1e5+10,M = 2e5+10;
int h[N],e[M],ne[M],idx;
struct node{
	int id,d;
};
vector<node> q[N];
int n,m;
int ans[N];
void add(int a,int b){
	ne[idx] = h[a],e[idx] = b,h[a] = idx++;
}
int dep[N],siz[N],top[N],fa[N][22],son[N];
int L[N],R[N],Index[N],dfn;
int cnt[N],skp;
void dfs1(int u,int f){
	dep[u] = dep[f] + 1;
	fa[u][0] = f;
	siz[u] = 1;
	L[u] = ++dfn;
	Index[dfn] = u;
	for(int i = 1;i<=20;i++) fa[u][i] = fa[fa[u][i-1]][i-1];
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(y == f)continue;
		dfs1(y,u);
		siz[u] += siz[y];
		if(siz[son[u]] < siz[y])son[u] = y;
	}
	R[u] = dfn;
}
void dfs2(int u,int v){
	top[u] = v;
	if(!son[u])return ;
	dfs2(son[u],v);
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(y != son[u] && y != fa[u][0])dfs2(y,y);
	}

}
int get(int x,int k){
	if(!k)return x;
	int id = log2(k);
	return get(fa[x][id] , k - (1<<id));
}
void add(int u){
	for(int i = L[u];i<=R[u];i++){
		if(Index[i] == skp){
			i = R[skp];
			continue;
		}
		cnt[dep[Index[i]]]++;
	}
}
void sub(int u){
	for(int i = L[u];i<=R[u];i++){
		cnt[dep[Index[i]]]=0;
	}
}
int dsu(int u){
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(y != fa[u][0] && y != son[u])dsu(y);
	}
	
	if(son[u]){
		dsu(son[u]);
		skp = son[u];
	}
	
	add(u);
	
	for(auto s : q[u]){
		int id = s.id,d = s.d;
 		ans[id] = cnt[d];
	}
	
	if(u == top[u]){
		skp = 0;
		sub(u);
	}
	
}
int main(){
	cin >> n ;
	memset(h,-1,sizeof h);
	for(int i = 2,a;i<=n+1;i++){
		scanf("%d",&a);
		if(a == 0)add(1,i),add(i,1);
		else add(a+1,i),add(i,a+1);
	}
	
	dfs1(1,0);
	dep[0] = -1;
	dfs2(1,0);

	cin >> m;
	int a,b;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&a,&b);
		a++;
		int fx = get(a,b);
		if(fx == 0 || fx == 1)ans[i] = 1;
		else
			q[fx].push_back({i,dep[fx] + b});
	}
	
	dsu(1);
	
	for(int i = 1;i<=m;i++)cout << ans[i] - 1 << " ";
	return 0;
}

over

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值