2020牛客暑期多校训练营(第十场) Decrement on the Tree

题目描述
有一棵包含 n n n个顶点和 n − 1 n-1 n1个边的树。 树中的每个边都有一个非负的权重。 每次都可以选择两个不同的顶点 u u u v v v,并将路径上每个边的权重减去 1 1 1。要使所有边的权重变为零。
最小操作数是多少 ? ?
您还需要支持边权重的修改 : : 将第 p p p个边的权重更改为 w w w。 每次修改后,您都需要输出答案。
样例
输入

5 3
1 2 3
1 3 4
2 4 5
3 5 6
1 10
2 10
3 10

输出

8
12
10
10

思路
首先我们会想到,要求最少的路径数,其实只要知道路径两端的节点的个数即可。因为边的个数 = = =总的个数 ÷ 2 ÷2 ÷2,所以我们直接考虑求节点的个数。
如果当前节点是路径节点满足的条件 : :

  • 当前节点的所连的边最大值要大于当前节点的所连的除最大值边的权值总和,举个例子 : :
    2020牛客暑期多校训练营(第十场) Decrement on the Tree_第1张图片
    1 1 1号节点所连的边最大值是 8 8 8,它所连的除最大值边的权值总和是 14 − 8 = 6 14-8=6 148=6
    所以其他的边都经过 1 1 1号结点连向 5 5 5号节点, 5 5 5号节点与 1 1 1号节点的边的权值为 2 2 2,所以 1 1 1号节点是 2 2 2个路径节点。
  • 当前节点所连边的权值总和是奇数,那么连完边后肯定至少有 1 1 1条边的权值不是 0 0 0,所以当前节点也是路径节点。

根据这个算法就可以求出路径节点的个数,它除以 2 2 2,就是边的数量。
最后考虑边的权值更改的情况 : :

  • 边的权值更改只改变边所连接的两个节点的路径节点的数量,所以只要改变这两个节点所连的这条边,重复以上算法即可。
    代码
#include
#define ll long long
using namespace std;
const int maxn=1e5+10;
multiset<ll>s[maxn];
multiset<ll>::iterator it;
int n,m,u,v,a[maxn],b[maxn],c[maxn];
ll k[maxn],ans=0;
ll js(int x)
{
	it=--(s[x].end());
	if(*it>k[x]-*it) return 2*(*it)-k[x];
	return k[x]&1;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i^n;i++)scanf("%d%d%d",&a[i],&b[i],&c[i]),k[a[i]]+=c[i],k[b[i]]+=c[i],s[a[i]].insert(c[i]),s[b[i]].insert(c[i]);
	for(int i=1;i<=n;i++)ans+=js(i);
	printf("%lld\n",ans/2ll);
	while(m--)
	{
		scanf("%d%d",&u,&v);
		ans-=js(a[u])+js(b[u]);
		k[a[u]]-=c[u];
		k[b[u]]-=c[u];
        s[a[u]].erase(s[a[u]].find(c[u]));
        s[b[u]].erase(s[b[u]].find(c[u]));
		k[a[u]]+=v;
		k[b[u]]+=v;
        s[a[u]].insert(v);
        s[b[u]].insert(v);
        c[u]=v;
        ans+=js(a[u])+js(b[u]);
		printf("%lld\n",ans/2);
	}
}

你可能感兴趣的:(2020牛客暑期多校训练营(第十场) Decrement on the Tree)