admin管理员组

文章数量:1622637

题目

n(n<=2e5)个点的树,点i有点权ai(1<=ai<=1e9)

"不同根"定义为,从该点出发,到任意点的路径上都不会经历两个相同的值

求图中"不同根"的个数

思路来源

凡神代码

题解

考虑到对于树上任意两个相同的值来说,

这两个点之外的点,都是不合法的点,

所以检查每两两个点即可,但是复杂度不符合要求

画画图发现,对于每个点来说,检查离它最近的相同的值的点即可

任选树根,对树进行dfs,记录dfs序,

对于点u来说,如果子树v里面存在和u相同的值,则需要对v子树以外的区间打+1标记

对于点u来说,如果子树u外存在和u相同的值,则需要对u子树内的区间打+1标记

一个点被打了多个标记是没关系的,只要保证合法的点没被打上标记即可

最后求一遍前缀和,统计没有标记的点的个数

对于判断u子树外和v子树内有没有和a[u]相同的值,

也可以启发式合并上来做,但感觉背离了这个思维题的初衷

代码

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=2e5+10;
map<int,int>to;
vector<int>e[N];
int n,a[N],u,v,x,ans;
int in[N],out[N],c;
int add,sum[N];
int up[N],cnt[N];
void dfs(int u,int fa){
	in[u]=++c;
	int pre=cnt[a[u]];
	for(auto &v:e[u]){
		if(v==fa)continue;
		int ppre=cnt[a[u]];
		dfs(v,u);
		int nnow=cnt[a[u]];
		if(nnow-ppre>0){//v子树有a[u] 对区间[in[v],out[v]]以外的区间打标 
			sum[1]++;
			sum[in[v]]--;
			sum[out[v]+1]++; 
		}
	}
	out[u]=c;
	cnt[a[u]]++;
	int now=cnt[a[u]];
	if(now-pre<up[a[u]]){//子树外有a[u] 对区间[in[u],out[u]]打标 
		sum[in[u]]++;
		sum[out[u]+1]--;
	}	
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		if(!to.count(a[i])){
			to[a[i]]=++x;
		}
	}
	for(int i=1;i<=n;++i){
		a[i]=to[a[i]];
		up[a[i]]++;
	}
	for(int i=1;i<n;++i){
		scanf("%d%d",&u,&v);
		e[u].pb(v);e[v].pb(u);
	}
	dfs(1,-1);
	for(int i=1;i<=n;++i){
		add+=sum[i];
		if(!add){
			ans++;
		} 
	}
	printf("%d\n",ans);
    return 0;
}

本文标签: 树上思维差分Divcodeforces