【Codeforces Round #695 (Div. 2) E】Distinctive Roots in a Tree
題目連結
翻譯
給你一棵樹,樹上的每一個節點都帶有權值。
讓你統計這樣的點 \(x\) 的個數,使得以 \(x\) 為根的時候,所有以 \(x\) 開始,以某個節點結束的路徑中每個節點的權值
都是唯一的,即每個權值都只出現了一次。
稱這樣的 \(x\) 為 \(distinctive\ root\), 統計所給的樹中這樣的 \(distinctive\ root\) 的個數。
題解
如圖,考慮樹中的每一個節點 \(x\), 對於它的某一個子樹 \(y\) ,我們可以看一下這個子樹 \(y\) 下面是否有和 \(x\) 的權值
相同的節點 \(z\),也即 \(a[x]=a[z]\)。如果存在,那麼就新增一條特殊的有向邊,從 \(x\)
這代表了,如果存在 \(distinctive\ root\), 那麼一定是在 \(y\) 子樹中的,因為如果不是在 \(y\) 子樹中,而在 \(x\) 其他子樹裡面。
一定會有一條路徑同時經過節點 \(x\) 然後順著子樹 \(y\) 到達節點 \(z\), 而 \(x\) 和 \(z\) 權值相同, 所以這就不滿足題意了。
按照這個思路,我們就在原來的樹上增加了一些特殊邊。
現在對於每一個節點,只要所有的特殊邊都指向了它(直接或間接),那麼這個節點就是能夠成為 \(distinctive\ root\) 的點。
累計答案就行。
具體在實現的時候,對於這個特殊邊的新增,我們需要先把每個節點的 \(dfs\)
根據這個 dfs序
,我們可以很容易的用 upper_bound
和 lower_bound
得到以某個節點為根的子樹下面有多少個權值為 \(x\) 的節點。
不要忘了,我們一開始的時候是以任意一個節點為根進行 \(dfs\) 的,所以除了統計 \(x\) 的子樹,還要把 \(x\) 以及它的祖先,也看做
是 \(x\) 的子樹,對應的節點 \(z\) 的個數也可以通過總數減子樹中數目的方式得到,然後決定是否要連一條特殊邊到父節點。
特殊邊建立好之後,就可以用一些 \(reroot\) 的方法,在做 \(dfs\) 的時候,根據加加減減動態的維護每個節點有多少條
特殊邊直接或間接指向它,對於所有邊都指向的點,累加答案即可。
具體的,設 \(dp[i]\) 表示 \(i\) 這個節點有多少條特殊邊指向它。然後維護這個陣列就好。
特殊邊是放在一個集合裡面的,這樣會比較好(方便)得到某條特殊邊是否存在。
吐槽一下,一開始我把 \(dfs\) 序中的某處的 \(in[x]\) 寫成了 \(x\),竟然能過 \(7\) 個點:)
程式碼裡寫了一點點註釋。嗯,好像不是一點點,蠻多的。
程式碼
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 2e5;
int n;
int a[N + 10],par[N+10],in[N+10],out[N+10],timeTip;
vector<int> g[N+10];
map<int,vector<int> > dic;
vector<int> inTimes;
set<pair<int,int> > edgeSet;
//dp[i]表示有多少條邊指向 i
int dp[N+10],ans;
void dfs(int x,int fa){
in[x] = ++timeTip;
int len = g[x].size();
for (int i = 0;i < len; i++){
int y = g[x][i];
if (y == fa){
continue;
}
par[y] = x;
dfs(y,x);
}
out[x] = ++timeTip;
}
void setUp(int x){
dp[x] = 0;
int len = g[x].size();
for (int i = 0;i < len; i++){
int y = g[x][i];
if (y == par[x]){
continue;
}
setUp(y);
dp[x] += dp[y] + edgeSet.count({y,x});
}
}
void getAns(int x){
if (dp[x] == (int)edgeSet.size()){
ans++;
}
int len = g[x].size();
for (int i = 0;i < len; i++){
int y = g[x][i];
if (y == par[x]){
continue;
}
dp[x] -= dp[y];
dp[x] -= edgeSet.count({y,x});
dp[y] += dp[x];
dp[y] += edgeSet.count({x,y});
getAns(y);
dp[y] -= dp[x];
dp[y] -= edgeSet.count({x,y});
dp[x] += dp[y];
dp[x] += edgeSet.count({y,x});
}
}
int main(){
#ifdef LOCAL_DEFINE
freopen("in.txt", "r", stdin);
#endif
ios::sync_with_stdio(0),cin.tie(0);
cin >> n;
for (int i = 1;i <= n; i++){
cin >> a[i];
dic[a[i]].push_back(i);
}
for (int i = 1;i <= n-1; i++){
int x, y;
cin >> x >> y;
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1,0);
for (pair<int,vector<int> > temp : dic){
if ((int) temp.second.size() == 1){
continue;
}
inTimes.clear();
for (int x:temp.second){
inTimes.push_back(in[x]);
}
sort(inTimes.begin(),inTimes.end());
//以節點 x 為根
for (int x:temp.second){
//統計子樹中和它相同的節點的個數(以 1 節點為根時的結果)
int sum = 0;
int len = g[x].size();
for (int i = 0;i < len; i++){
int y = g[x][i];
if (y == par[x]){
continue;
}
//in[y],out[y]
int num = upper_bound(inTimes.begin(),inTimes.end(),out[y])-
lower_bound(inTimes.begin(),inTimes.end(),in[y]);
if (num > 0){
//對應子樹中有 a[x],則從對應子樹的根節點 y 連一條邊到 x
edgeSet.insert({x,y});
}
sum += num;
}
//算上本身。
sum++;
//x的父節點以上的 a[x] 個數
int rest = (int)temp.second.size() - sum;
if (rest > 0){
//如果也有,那麼也從x連一條邊到 父節點
edgeSet.insert({x,par[x]});
}
}
}
//求出 dp 陣列
setUp(1);
//用reroot的方法求出符合要求點數。
getAns(1);
cout << ans << endl;
return 0;
}