欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

2020牛客暑期多校训练营Decrement on the Tree(图论,set)

程序员文章站 2022-06-02 22:41:38
...

Decrement on the Tree

题目描述

2020牛客暑期多校训练营Decrement on the Tree(图论,set)

样例

input:
5 3
1 2 3
1 3 4
2 4 5
3 5 6
1 10
2 10
3 10
output:
8
12
10
10

题目大意

给你一棵树,现在你可以选择其中的一条链,将其边上的权值都减一。并且每条边的权值不能为负数。要求最少要删除多少次(每次只能减一),才能使得整棵树的权值都是0。(只需要输出答案,而不是修改)
并且,在给出答案之后,会有qq次修改,将某一条边的权值改成另一个,这时你要再一次输出答案。

分析

首先看到题目有一种树剖的感觉,但是显然,求答案时无法很好地用树剖解决。

所以这题应该是要维护每条边的权值。我们不妨思考一下求答案的过程实际是什么。实际就是找最少的链将树覆盖,然后每条边的重叠次数都有限制。
由于每条链唯一地由两个端点决定,所以,我们不妨考虑一下一棵树被覆盖后的链的端点处在那些节点上。
2020牛客暑期多校训练营Decrement on the Tree(图论,set)
如图,我已经将覆盖所用的链用不同的颜色标出。可以发现:

  • 如果出现图三的情况,即一个点相连的边的权值中MaxMax大于其他边的和,那么其他边可以通过MaxMax通向其他的节点作为终点,而MaxMax减去其他边总和数量的链只有将这个点作为端点了。这样,以这个点为端点个数就是MaxOthersMax-Others
  • 如果图二的情况,即没有一条边是大于其他边权和(虽然上图也是,但是请假装2不大于1:)),那么就要考虑欧拉回路的知识,如果是奇数,那么肯定有一个端点,如果是图一一样的偶数,那么可以做到这个点上没有端点。

那么用上述的方法可以求得整棵树上的端点个数,那么除以2就是链的个数了。

然后考虑修改的操作,我们只要维护一个multisetmultiset,由于每条边只会影响到相连的两个节点。然后找到要修改的值,直接修改就可以了。这里就大大体现了STLSTL的优势美滋滋。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+10;
ll len[MAXN],sum[MAXN],ans=0;//len 第i条边的权值 sum 第i个点的与之相连的边权和 ans 端点个数
pair<ll,ll> edge[MAXN];//第i条边的两个端点
multiset<ll> du[MAXN];//点相连的每条边权
multiset<ll>::iterator it;//迭代器
ll getnum(int x){//获取第x个节点的端点数
	it=--(du[x].end());//Max
	if(*it>sum[x]-*it) return *it-(sum[x]-*it);//如果Max大于其他点
	return sum[x]&1;//如果点没有出现上述情况,考虑奇数偶数
}
void update(int id,int x,ll w){//修改的边编号为id,当前考虑的点为x,修改为w
	ans-=getnum(x);//减去当前节点的
	du[x].erase(du[x].find(len[id]));du[x].insert(w);//删除之前这条边,然后插入新的
	sum[x]=sum[x]-len[id]+w;//更新x节点的边权和
	ans+=getnum(x);//重新算当前节点的端点数,并加入答案
}
int main()
{
	ll n,q,id,u,v,w;
	scanf("%lld%lld",&n,&q);
	if(n==1){puts("0");while(q--) puts("0");return 0;}
	//SPJ没有边的情况,虽然没有这组测试数据,但是还是要加上的
	//如果没有边,multiset的末尾指针减一会炸掉
	for(int i=1;i<n;i++){
		scanf("%lld%lld%lld",&u,&v,&w);
		edge[i]=make_pair(u,v);len[i]=w;
		du[u].insert(w);du[v].insert(w);
		sum[u]+=w;sum[v]+=w;
	}//读入
	for(int i=1;i<=n;i++)
		ans+=getnum(i);
	printf("%lld\n",ans/2);//先算当前的答案
	while(q--){
		scanf("%lld%lld",&id,&w);
		ll u=edge[id].first,v=edge[id].second;//取出边的两个节点
		update(id,u,w);update(id,v,w);//修改答案和点的边权和
		len[id]=w;//修改边权
		printf("%lld\n",ans/2);
	}
}

END

multisetmultiset万岁!!!