题目链接:点击查看
题目大意:给出一棵 n 个点组成的树,每条边上都有边权,现在可以进行数次操作,每次操作可以选择一条路径,使得路径上的权值减一,问最少需要进行多少次操作才能使得所有的边权变为 0 ,输出这个操作次数,再给出 m 次询问,每次询问会修改一条边权,每次需要回答修改边权后的答案
题目分析:读完题的第一感觉是树形dp然后用树剖+线段树优化,事实证明确实可以写,但我不会写
讲一下官方题解的做法吧,非常需要思维,首先需要将边权转换为点权,对于每次操作选取一条路径然后将其路径上的边权减一,对应过来就是将两个端点遍历一次,这样问题就转换为了,将所有边权变为 0 时,令每个端点被访问的次数之和最小
对于每个点计算贡献,就会发现有两种情况:假设 mmax 为与当前点相连的所有边中,权值最大的边权,sum 为与当前点相连的所有边权之和
- mmax <= ( sum - mmax ):此时肯定有一种匹配方法使得边权两两互相匹配,换句话说,当前点不需要作为端点与其他端点形成路径,只需要作为中间点被经过就好,这样的贡献就是 sum%2 ,因为如果 sum 为奇数的话,会有一条边权失配,那么只能由该点作为端点一次
- mmax > ( sum - mmax ):此时如果其余所有的边都与 mmax 匹配的话,最终还是会剩下 mmax - ( sum - mmax ) 的边权失配,那么需要当前点作为端点这么多次才行
对于每次修改,只需要维护一个 multiset 实时计算贡献就好了
最后的答案记得除以 2 ,因为当边权映射给点权后,是一条边权对应着两个端点,我们维护的 ans 是最少点的贡献,转换为边的贡献当然应该除以 2 了
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<unordered_map>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=1e5+100;
multiset<int>st[N];
LL sum[N];
int x[N],y[N],w[N];
void del(int p)
{
int x=::x[p],y=::y[p],w=::w[p];
st[x].erase(st[x].find(w));
st[y].erase(st[y].find(w));
sum[x]-=w;
sum[y]-=w;
}
void add(int p)
{
int x=::x[p],y=::y[p],w=::w[p];
st[x].insert(w);
st[y].insert(w);
sum[x]+=w;
sum[y]+=w;
}
LL cal(int p)
{
int mmax=*st[p].rbegin();
if(mmax*2>sum[p])
return mmax*2-sum[p];
else
return sum[p]&1;
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
scanf("%d%d%d",x+i,y+i,w+i);
add(i);
}
LL ans=0;
for(int i=1;i<=n;i++)
ans+=cal(i);
printf("%lld\n",ans/2);
while(m--)
{
int pos,val;
scanf("%d%d",&pos,&val);
ans-=cal(x[pos])+cal(y[pos]);
del(pos);
w[pos]=val;
add(pos);
ans+=cal(x[pos])+cal(y[pos]);
printf("%lld\n",ans/2);
}
return 0;
}