Codeforces Round #695 (Div. 2) 全文见:https://blog.csdn.net/qq_43461168/article/details/112598001
E. Distinctive Roots in a Tree
题意:输出符合条件的点的个数。条件:从这个点出发到任意一个点的路径中没有重复的值。
思路参考:https://blog.csdn.net/forever_shi/article/details/112467674
思路:反过来想。 题目要求符合条件的点的个数。 但是枚举每个点是否合法显然不合理。 但是判断一个点不合法比较简单。所以可以标记所有的不合法点。剩下的就是答案了。怎么标记呢。 假设当前点为pos,a[pos] 为 x 。考虑两种情况。
1. pos 的后代(某个子树中的某个节点) y == x。 那么除了 dfs序位于[pos - y] 之间的点。其他的点一定是不合法的。其他的点必然会经过这两个 x 的。显然不合法。所以要标记除这条路径上的所有点。 用到树上差分。利用dfs序可以很方便的找出 dfn[pos] - dfn[y] 的所有点。
2. 除了子树和自己以外。 还存在x,那么pos的所有子树都不合法。因为这说明,连接父亲的那条边过去,还会碰到一个x。那么如果以pos 的子节点为根。必然会经过pos 和 另一个x。 所以,同样用差分把pos所有子树打上标记。
还有一个小问题就是,数值比较大,可以开map计数。也可以离散化之后计数。 离散化比map快一些。
AC代码:
#include <iostream>
#include <bits/stdc++.h>
#define int long long
#define mk make_pair
#define gcd __gcd
using namespace std;
const double eps = 1e-10;
const int mod = 1e9+7;
const int N = 3e5+7;
int n,m,k,t = 1,cas = 1;
int a[N],b[N];
int cnt[N]; // 计数
int vis[N]; // 计数
int in[N]; // dfs 进入序号
int out[N]; // dfs 退出序号
int sum[N]; // 差分数组
vector<int> edge[N];
vector<int> values;
int tot = 0;
void dfs(int pos,int fa){
in[pos] = ++tot;
int cntt = vis[a[pos]] ++;
for(int i = 0 ; i < edge[pos].size() ; i ++){
int to = edge[pos][i];
if(to == fa) continue;
int now = vis[a[pos]];
dfs(to,pos);
if(vis[a[pos]] > now){
sum[1] ++;
sum[n+1] ++;
sum[in[to]] --;
sum[out[to]+1] ++;
}
}
out[pos] = tot;
if(vis[a[pos]] - cntt != cnt[a[pos]]){
sum[in[pos]] ++;
sum[out[pos]+1] --;
}
}
signed main(){
cin>>n;
for(int i = 1 ; i <= n ; i ++){
cin>>a[i];
values.push_back(a[i]);
}
for(int i = 0 ; i < n-1 ; i ++){
int x,y;
cin>>x>>y;
edge[x].push_back(y);
edge[y].push_back(x);
}
sort(values.begin(),values.end());
values.erase(unique(values.begin(),values.end()),values.end());
for(int i = 1 ; i <= n ; i ++){
a[i] = lower_bound(values.begin(),values.end(),a[i])-values.begin();
cnt[a[i]] ++;
}
dfs(1,-1);
int ans = 0;
for(int i = 1; i <= n ; i ++){
sum[i] += sum[i-1];
if(sum[i] == 0){
ans ++;
}
}
cout<<ans<<endl;
return 0;
}