Codeforces Round #695 (Div. 2) E. Distinctive Roots in a Tree(思维题-树上差分)

题目

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值