D.Decrement on the Tree
给定一颗无根树,有n个结点,n-1条边,每条边有一个权值。
修改操作:选择两个点,两个点之间的最短路径上的边权减一。
询问:将第i条边的权值赋值为x,询问要执行多少次修改操作可以使得所有边权变为零。
分析:修改操作可以看成将树划分成若干条路径。每个点将它所连接的边一一匹配起来。那么我们只需要计算路径的起点和终点个数就可以得到路径的条数。
对于一个点,如果所连接的边中,没有一条边的边权超过连接边的所有边权一半,那么一定可以两两匹配(会根据奇偶性多出至多一条,那么这条边的起点就是该点).如果超过了一半,那么未匹配的边就是2 * max-sum,这些边的起点就是该点。
修改可以用个set简单维护。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn=1e5+10;
multiset<ll> s[maxn];
multiset<ll> :: iterator it;
int n,q,a[maxn],b[maxn];
ll w[maxn],sum[maxn],ans;
ll date( int x )
{
it=s[x].end();
--it;
ll maxn=*it;
if( maxn*2>sum[x] ) return 2*maxn-sum[x];
return sum[x]&1;
}
int main()
{
scanf("%d%d",&n,&q);
for( int i=1;i<n;i++ )
{
scanf("%d%d%lld",a+i,b+i,w+i);
s[a[i]].insert(w[i]);
s[b[i]].insert(w[i]);
sum[a[i]]+=w[i];
sum[b[i]]+=w[i];
}
for( int i=1;i<=n;i++ )
{
ans+=date(i);
}
printf("%lld\n",ans/2ll);
while( q-- )
{
int p;ll val;
scanf("%d%lld",&p,&val);
ans-=date(a[p])+date(b[p]);
s[a[p]].erase(s[a[p]].find(w[p]));
s[b[p]].erase(s[b[p]].find(w[p]));
s[a[p]].insert(val);
s[b[p]].insert(val);
sum[a[p]]+=val-w[p];
sum[b[p]]+=val-w[p];
w[p]=val;
ans+=date(a[p])+date(b[p]);
printf("%lld\n",ans/2ll);
}
}
京公网安备 11010502036488号