【題解】P4178 Tree

前置芝士

P3806 【模板】點分治1 。不過數據真是水的可以,第一次我數組開小,過了;第二次我分治的時候沒找中心,還是過了……所以也可以做P4149 Race

題意

和點分治模板很像:求樹上距離小於等於kk的路徑數量。(把模板的等於改成了小於等於,並且需要統計路徑數量)

分析

由於題目變成了小於等於,那麼我們就不能再用原來那套開桶的辦法了。於是我們考慮把當前根的所有子樹中的節點拉出來統計方案。

具體方法是把所有子樹中的節點按照到根的距離排序,然後兩端開始雙指針O(n)O(n)掃描。(不會雙指針的自行GoogleGoogle


於是你自信滿滿地打了一發,卻發現爆00了!!!

實際上是由於有一些情況沒有考慮到。

由於普通點分治的時候是對根的每一棵子樹分別進行答案統計,也就意味着統計的路徑都是不同子樹間的,但我們這次把所有點都拉出來排序了,也就不能保證這條路徑橫跨兩棵子樹。於是就會產生下圖的情況:

V24JYV.png

途中藍色路徑雖然經過科根節點,但顯然是不合法的路徑,需要減去。

這裏用到一點小小的容斥,由於這種不合法的路徑一定在根節點的同一棵子樹內,於是我們先計算出經過這個子樹的根的路徑數量(上圖中子樹的根是1),然後減去即可。而對於子樹中出現的不合法情況,我們接着用同樣的方法容斥即可。

注意點:容斥的時候並不是真正統計子節點答案的時候,計算之前需要把兒子的disdis值設爲當前根節點到它的邊權,因爲這樣的不合法路徑一定會到達根節點,在統計的時候必須把經過兩次的那條多餘邊減去。

代碼

#include <bits/stdc++.h>
#define MAX 100005
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;

int n, k, cnt, rt, sum, tot;
int head[MAX], vet[MAX], Next[MAX], cost[MAX];
int dis[MAX], d[MAX], mx[MAX], sz[MAX], vis[MAX];
ll ans;

void add(int x, int y, int w){
    cnt++;
    Next[cnt] = head[x];
    head[x] = cnt;
    vet[cnt] = y;
    cost[cnt] = w;
}

void getrt(int x, int fa){		//找重心(點分治模板)
    sz[x] = 1, mx[x] = 0;
    for (int i = head[x]; i; i = Next[i]) {
        int v = vet[i];
        if(v == fa || vis[v]) continue;
        getrt(v, x);
        sz[x] += sz[v];
        mx[x] = max(mx[x], sz[v]);
    }
    mx[x] = max(mx[x], sum-sz[x]);
    if(mx[x] < mx[rt]) rt = x;
}

void getdis(int x, int fa){		//處理距離(模板)
    d[++tot] = dis[x];
    for (int i = head[x]; i; i = Next[i]) {
        int v = vet[i];
        if(v == fa || vis[v]) continue;
        dis[v] = dis[x]+cost[i];
        getdis(v, x);
    }
}

ll calc(int x, int w){		//計算貢獻
    tot = 0;
    dis[x] = w;
    getdis(x, 0);		//處理出距離並排序
    sort(d+1, d+tot+1);
    int l = 1, r = tot;
    ll res = 0;
    while(l < r){		//雙指針,從兩頭開始掃
        if(d[l]+d[r] <= k){
            res += r-l;
            l++;
        }
        else r--;
    }
    return res;
}

void solve(int x){
    vis[x] = 1;
    ans += calc(x, 0);
    for (int i = head[x]; i; i = Next[i]) {
        int v = vet[i];
        if(vis[v]) continue;
        ans -= calc(v, cost[i]);		//容斥,把兒子節點dis初值設爲邊權
        sum = sz[v];
        rt = 0, mx[rt] = INF;
        getrt(v, 0);
        solve(rt);
    }
}

int main()
{
    cin >> n;
    int x, y, w;
    for (int i = 1; i < n; ++i) {
        scanf("%d%d%d", &x, &y, &w);
        add(x, y, w);
        add(y, x, w);
    }
    cin >> k;
    mx[rt] = sum = n;
    getrt(1, 0);
    solve(rt);

    cout << ans << endl;

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章