ARC098F Donation
传送门
Solution
首先是几个引理:
- 重新定义权值\(val_i=max(a_i-b_i,0)\),那么通过这个点必须需要\(val_i+b_i\)的钱。
- 多次经过一个点一定是在最后一次捐赠。
- 我们按照\(val_i\)排序那么大的一定先访问。
第1个可以感性理解,第2个很显然,这里主要理性证明一下第3个引理:
设当前钱数为\(x\),先后经过\(i,j\)两个站点且\(a_i-b_i<a_j-b_j\)。
那么两种情况分别对应:
-
\(x \ge b_i+a_j\)
-
\(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;
}