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;
}