2020牛客多校第10場C Decrement on the Tree樹上路徑刪除
題意:
給定一棵樹,一次操作可以把一條路徑上每一條邊的邊權都-1,問最少進行多少次操作能把整棵樹的邊權都變為0。
題解:
把刪邊操作轉化為訪問點操作,易知當把一條邊的邊權-1時,這條邊的兩個端點都會被訪問到1次。這樣一來可以把問題轉化成最少訪問多少次點能夠把所有邊權變為0。
對於一個點來說,他對答案的貢獻是怎樣的呢?
一個點連著很多條邊,有一個結論就是,如果一個點所連的邊權中最大的那個邊權mx如果小於這個點所連線的邊權總和sum的一半時,那麼這個點所連線的這些便可以相互之間操作變為0,只需要判一下sum的奇偶,若為奇則需要多一次操作消去剩餘的那1邊權 ;否則則需要額外的2*mx-sum次來消去其不能與這個點其他相連的邊消去的部分。
因為容易發現,對於葉子結點,你想要讓他所連的邊變成0,那就一定要操作邊權次操作,葉子節點總符合mx2>=sum的情況。對於非葉子節點,則有可能符合mx2>=sum,也可能不符合,這是因為,我們在對這棵樹上原有的葉子節點進行操作時,可能會操作到一定次數使得一些邊權變為0,從而相當於這條邊斷掉,而產生新的葉子節點,而我們知道葉子節點所連的邊權是要全部加上的,只不過此時的葉子節點是新產生的,他所連的邊權也是更新過後的。這就是對於非葉子結點,mx2>=sum時要加上2mx-sum,mx*2<sum時若sum為奇數需要+1,都是因為這個結點在滿足以上情況時會在某個時刻變成一個葉子節點!而葉子節點所連的邊權需要全部加上!
以上就是我對於這道題目的理解,我明白我講的非常抽象,有點懶不想畫圖了!還是不是很明白的小夥伴請自行畫圖舉例理解一下子哈(我也是自己看部落格畫圖理解的!
感悟:
這道題感覺處理得真的非常巧妙吧!感覺把樹上邊權問題很多都是轉化成點權的問題來做的,感覺這是樹上很多問題的一個套路吧!希望做過這個題之後能給以後做其他一些題提供一些思路啦!
程式碼:
/**************************** * Author : W.A.R * * Date : 2020-08-10-20:47 * ****************************/ /* 1.int型別資料用%lld輸入會re 2.multiset刪除不存在的東西會re */ #include<stdio.h> #include<string.h> #include<math.h> #include<algorithm> #include<queue> #include<map> #include<stack> #include<string> #include<set> using namespace std; typedef long long ll; const int maxn=1e5+50; const ll mod=1e9+7; multiset<int>s[maxn]; int x[maxn],y[maxn],z[maxn];ll v[maxn]; int cal(int i){ int tt=*s[i].rbegin();if(2*tt>=v[i])return 2*tt-v[i];else return v[i]&1; } int main(){ int n,q;ll ans=0;scanf("%d%d",&n,&q); for(int i=1;i<n;i++){ scanf("%d%d%d",&x[i],&y[i],&z[i]); v[x[i]]+=z[i];v[y[i]]+=z[i]; s[x[i]].insert(z[i]);s[y[i]].insert(z[i]); } for(int i=1;i<=n;i++)ans+=cal(i);printf("%lld\n",ans/2); while(q--){ int p,w;scanf("%d%d",&p,&w); ans-=cal(x[p])+cal(y[p]); s[x[p]].erase(s[x[p]].find(z[p]));s[y[p]].erase(s[y[p]].find(z[p]));v[x[p]]-=z[p];v[y[p]]-=z[p]; z[p]=w;//這一步很重要不能少,因為修改的時候要用到z陣列的!!! s[x[p]].insert(w);s[y[p]].insert(w);v[x[p]]+=w;v[y[p]]+=w; ans+=cal(x[p])+cal(y[p]);printf("%lld\n",ans/2); } return 0; }