2020牛客多校十C-Decrement on the Tree

原题链接
在这里插入图片描述
意思就是给一颗树,你可以操作若干次,每次操作可使一条路径上经过的边的边权-1,你要使所有边的边权为0,求最小操作次数。完成之后题目还会修改边权,对于每次修改,你需要再次给出答案。

分析

边权转化为点权,不如把减边权的操作转化为增加路径的两个端点的访问次数,那所求即转化为求最少的访问次数。
设一个点为 c c c s u m sum sum为所有与 c c c点连接的边的权值的和。我们用 ∗ t *t t来保存 s u m sum sum中最大边权的值。如果 ∗ t > s u m − ∗ t *t>sum-*t t>sumt,其余所有边都与最大边匹配也会剩下 ∗ t ∗ 2 − s u m *t*2-sum t2sum的边权失配。当前的点必须作为端点这些次才能把边权减为 0 0 0,这样 g e t s u m ( c ) getsum(c) getsum(c)的值就是 ∗ t ∗ 2 − s u m *t*2-sum t2sum。如果 ∗ t < = s u m − ∗ t *t<=sum-*t t<=sumt,肯定存在匹配方法使边权两两匹配,只需让 c c c点作为中间点被经过即可,若最大边权为奇数,会有一个边权失配,需以当前点作为端点形成一条路径才行,这样 g e t s u m ( c ) getsum(c) getsum(c)的值就是 s u m sum sum% 2 2 2
因为减边减一次增加点访问数是 2 2 2,所以 总 访 问 数 / 2 总访问数/2 访/2才是真正的结果。
修改边权的操作也很简单,减去边的两个端点在答案中的值,删除边,增加新边再算一次 g e t s u m getsum getsum就行了。

#include<iostream>
#include<set>
#define ll long long
using namespace std;
const int N=1e5+10;
ll sum[N];
int u[N],v[N],p[N];
int x,y;
multiset<ll>s[N];
ll ans;
ll getsum(ll c)
{
	auto t=--(s[c].end());
	if(*t>sum[c]-*t)return *t*2-sum[c];
	if(sum[c]%2)return 1;
	return 0;
}
void update(int c)
{
	ans-=getsum(c);
	sum[c]+=-p[x]+y;
	s[c].erase(s[c].find(p[x]));
	s[c].insert(y);
	ans+=getsum(c);
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n,q;
	cin>>n>>q;
	for(int i=1;i<n;i++)
	{
		cin>>u[i]>>v[i]>>p[i];
		sum[u[i]]+=p[i];
		sum[v[i]]+=p[i];
		s[u[i]].insert(p[i]);
		s[v[i]].insert(p[i]);
	}
	for(int i=1;i<=n;i++)
		ans+=getsum(i);
	cout<<ans/2<<'\n';
	for(int i=1;i<=q;i++)
	{
		cin>>x>>y;
		update(u[x]);
		update(v[x]);
		p[x]=y;
		cout<<ans/2<<'\n';
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值