1. 程式人生 > 實用技巧 >【Codeforces Round #695 (Div. 2) E】Distinctive Roots in a Tree

【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\)

指向 \(y\)

這代表了,如果存在 \(distinctive\ root\), 那麼一定是在 \(y\) 子樹中的,因為如果不是在 \(y\) 子樹中,而在 \(x\) 其他子樹裡面。

一定會有一條路徑同時經過節點 \(x\) 然後順著子樹 \(y\) 到達節點 \(z\), 而 \(x\)\(z\) 權值相同, 所以這就不滿足題意了。

按照這個思路,我們就在原來的樹上增加了一些特殊邊。

現在對於每一個節點,只要所有的特殊邊都指向了它(直接或間接),那麼這個節點就是能夠成為 \(distinctive\ root\) 的點。

累計答案就行。

具體在實現的時候,對於這個特殊邊的新增,我們需要先把每個節點的 \(dfs\)

序求出來,就是先序遍歷的時候的順序。

根據這個 dfs序,我們可以很容易的用 upper_boundlower_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;
}