1. 程式人生 > 實用技巧 >2020暑假牛客多校10 C -Decrement on the Tree (邊權轉點權處理)

2020暑假牛客多校10 C -Decrement on the Tree (邊權轉點權處理)

C Decrement on the Tree

參考部落格

題目:

一棵樹每次選擇一條路徑將路徑上的邊權都減1,問最少多少次操作後所有邊權變成0。

題解:

看了好幾篇部落格才明白了,這道題的做法是將對邊權的處理轉變成去想對點的處理,算各點的貢獻。之所以可以這樣做是基於給一條邊的邊權-1,相當於訪問這條邊所連2個點各1次。

如果不轉換思路去考慮各邊對結果的貢獻,邊的邊權知道,兩點知道,其他的如選的路徑每次是什麼都不清楚,所以會有一種思路很”窄“的感覺。但是換作考慮點的貢獻,對每一個點,它連著好幾條邊。任意兩個邊都可以形成路徑,將所有邊減為0相當於這個點所連的邊考慮邊的權值關係下相互匹配,至於什麼關係下面討論,反正思路是很開闊的。這應該算是一種處理圖問題的重要思路,當然有時候也把點的權值問題轉為邊的問題。

討論一個點各邊權值關係的處理:

(1) 最好想情況是 一個邊權超級大,那麼其他邊都和這一邊組成路徑把其他邊消為0,最後肯定這條邊無法”蹭“別的路徑去消除,只能自己消除,這就是當前點的貢獻。設當前點為u, 其所連的總邊權為sum , 超級大邊權為maxx 。得該點貢獻為:(sum-maxx是除maxx外其它邊權和)

\[maxx - (sum - maxx) = 2*maxx-sum \]

容易發現maxx >= sum / 2 時就屬於情況(1) 。 感性理解就是其他邊權和加起來都不能消除完maxx時是情況(1)

(2) 如果maxx < sum / 2, 且u相連邊總邊權sum 為偶數時,肯定可以相互匹配完。貢獻為0. sum

為奇數時,最後一定會剩餘1需要u作為貢獻去處理掉。

這樣遍歷一遍每個結點就可以求出中貢獻,但是一個邊對應2個點,所以算的結果應該除2才是對邊得最少運算元。

至於q次詢問,每次O(1) 修改一下p邊所連點得資訊,重新算一下該邊相連兩點得貢獻就可以算出答案。

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define rep(i, a, n) for(int i = a; i <= n; ++ i);
#define per(i, a, n) for(int i = n; i >= a; -- i);
typedef long long ll;
const int N = 2e6+ 5;
const int mod = 998244353;
const double Pi = acos(- 1.0);
const int INF = 0x3f3f3f3f;
const int G = 3, Gi = 332748118;
ll qpow(ll a, ll b) { ll res = 1; while(b){ if(b) res = (res * a) % mod; a = (a * a) % mod; b >>= 1;} return res; }
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
ll lcm(ll a, ll b) { return a * b / gcd(a, b);}
bool cmp(int a, int b){ return a > b;}
//

int n, q; ll res;
multiset<int> sol[N];
int x[N], y[N];
ll w[N], val[N];

int cal(int i){	//計算每點貢獻
    int tp = *sol[i].rbegin();
    if(2 * tp >= val[i]) return 2 * tp - val[i];
    else return val[i] & 1;
}

int main()
{
    scanf("%d%d",&n,&q);
    for(int i = 1; i < n; ++ i){
        scanf("%d%d%lld",&x[i],&y[i],&w[i]);
        val[x[i]] += w[i], val[y[i]] += w[i];
        sol[x[i]].insert(w[i]); sol[y[i]].insert(w[i]);
    }
    for(int i = 1; i <= n; ++ i) res += cal(i);
    printf("%lld\n",res / 2);
    
    while(q --){    //q次詢問
        int p; ll tw; scanf("%d%lld",&p,&tw);
        res -= cal(x[p]) + cal(y[p]);
        val[x[p]] -= w[p]; val[y[p]] -= w[p];
        sol[x[p]].erase(sol[x[p]].find(w[p])); 
        sol[y[p]].erase(sol[y[p]].find(w[p]));
        
        w[p] = tw;
        val[x[p]] += tw; val[y[p]] += tw;
        sol[x[p]].insert(tw); sol[y[p]].insert(tw);
        res += cal(x[p]) + cal(y[p]);
        printf("%lld\n",res / 2);
    }
    return 0;
}