ARC098F Donation

传送门

Atcoder

Solution

首先是几个引理:

  1. 重新定义权值\(val_i=max(a_i-b_i,0)\),那么通过这个点必须需要\(val_i+b_i\)的钱。
  2. 多次经过一个点一定是在最后一次捐赠。
  3. 我们按照\(val_i\)排序那么大的一定先访问。

第1个可以感性理解,第2个很显然,这里主要理性证明一下第3个引理:

设当前钱数为\(x\),先后经过\(i,j\)两个站点且\(a_i-b_i<a_j-b_j\)

那么两种情况分别对应:

  1. \(x \ge b_i+a_j\)

  2. \(x \ge b_j+a_i\)

又因为\(a_i-b_i<a_j-b_j\),所以\(a_i+b_j<a_j+b_i\)

所以走下面这种一定更优。

那么这个时候我们可以根据\(val_i\)将原图分成多个联通块,联通块内再分就是一棵树是吧(类似淀粉质)。

在这个树上\(dp\)即可。

考虑设\(f_i\)表示做完以\(i\)为根的子树的最小钱数,\(g_i=f_i-\sum_{son \in i}b_i\)

我们很显然可以转移\(g\),然后再用\(g\)转移\(f\)即可。

Code

/*
  mail: mleautomaton@foxmail.com
  author: MLEAutoMaton
  This Code is made by MLEAutoMaton
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<iostream>
using namespace std;
#define ll long long
#define REP(a,b,c) for(int a=b;a<=c;a++)
#define re register
#define int ll
#define file(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
inline int gi(){
	int f=1,sum=0;char ch=getchar();
	while(ch>'9' || ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0' && ch<='9'){sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return f*sum;
}
const int N=200010,Inf=1e18+10;
int n,m,a[N],b[N],sum[N],f[N],p[N],dp[N],vis[N];
int find(int x){return f[x]!=x?f[x]=find(f[x]):f[x];}
vector<int>G[N];
bool cmp(int A,int B){return a[A]<a[B];}
signed main(){
	n=gi();m=gi();
	for(int i=1;i<=n;i++){
		a[i]=gi();b[i]=gi();a[i]=max(a[i]-b[i],0ll);
		p[i]=f[i]=i;sum[i]=b[i];dp[i]=a[i];
	}
	for(int i=1;i<=m;i++){int u=gi(),v=gi();G[u].push_back(v);G[v].push_back(u);}
	sort(p+1,p+n+1,cmp);
	for(int i=1;i<=n;i++){
		int u=p[i];vis[u]=1;
		for(int v:G[u])
			if(vis[v]){
				int x=find(u),y=find(v);
				if(x!=y){
					f[y]=x;sum[x]+=sum[y];
					dp[x]=min(dp[x],max(dp[y],a[x]-sum[y]));
				}
			}
	}
	printf("%lld\n",dp[find(1)]+sum[find(1)]);
	return 0;
}
posted @ 2019-10-15 17:24  fexuile  阅读(349)  评论(0编辑  收藏  举报