牛客多校10 - Decrement on the Tree(边权转点权+思维)

题目链接:点击查看

题目大意:给出一棵 n 个点组成的树,每条边上都有边权,现在可以进行数次操作,每次操作可以选择一条路径,使得路径上的权值减一,问最少需要进行多少次操作才能使得所有的边权变为 0 ,输出这个操作次数,再给出 m 次询问,每次询问会修改一条边权,每次需要回答修改边权后的答案

题目分析:读完题的第一感觉是树形dp然后用树剖+线段树优化,事实证明确实可以写,但我不会写

讲一下官方题解的做法吧,非常需要思维,首先需要将边权转换为点权,对于每次操作选取一条路径然后将其路径上的边权减一,对应过来就是将两个端点遍历一次,这样问题就转换为了,将所有边权变为 0 时,令每个端点被访问的次数之和最小

对于每个点计算贡献,就会发现有两种情况:假设 mmax 为与当前点相连的所有边中,权值最大的边权,sum 为与当前点相连的所有边权之和

  1. mmax <= ( sum - mmax ):此时肯定有一种匹配方法使得边权两两互相匹配,换句话说,当前点不需要作为端点与其他端点形成路径,只需要作为中间点被经过就好,这样的贡献就是 sum%2 ,因为如果 sum 为奇数的话,会有一条边权失配,那么只能由该点作为端点一次
  2. mmax > ( sum - mmax ):此时如果其余所有的边都与 mmax 匹配的话,最终还是会剩下 mmax - ( sum - mmax ) 的边权失配,那么需要当前点作为端点这么多次才行

对于每次修改,只需要维护一个 multiset 实时计算贡献就好了

最后的答案记得除以 2 ,因为当边权映射给点权后,是一条边权对应着两个端点,我们维护的 ans 是最少点的贡献,转换为边的贡献当然应该除以 2 了

代码:
 

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
 
typedef long long LL;
 
typedef unsigned long long ull;
 
const int inf=0x3f3f3f3f;
 
const int N=1e5+100;

multisetst[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

 

你可能感兴趣的:(图论,思维)