[ARC098F] Donation(找性质+点 Kruskal 重构树)

[ARC098F] Donation

给出一个 \(N\) 个点 \(M\) 条边的无向连通图,每个点的标号为 \(1\)\(n\), 且有两个权值 \(A_i,B_i\)。第 \(i\) 条边连接了点 \(u_i\)\(v_i\)

最开始时你拥有一定数量的钱,并且可以选择这张图上的任意一个点作为起始点,之后你从这个点开始沿着给定的边遍历这张图。每当你到达一个点 \(v\) 时,你必须拥有至少 \(A_v\) 元。而当你到达了这个点后,你可以选择向它捐献 \(B_v\) 元(当然也可以选择不捐献),当然,你需要保证在每次捐献之后自己剩余的钱\(\geq 0\)

你需要对所有的 \(n\) 个点都捐献一次,求你一开始至少需要携带多少钱。

\(1\le N\le 10^5,N-1\le M\le 10^5,1\le A_i,B_i\le 10^9,1\le u_i<v_i\le N\),保证题目给出的图联通。

考虑倒着来,那么每个点的限制变成了需要至少 \(C_i=\max\{A_i-B_i,0\}\) 钱才能到达,到了之后能够得到 \(B_i\) 块钱。

\(\bigstar\texttt{Hint}\):后面没有想到的主要原因是,没发现如果到达了一个 \(C_i\) 的点,则所有 \(\le C_i\) 的点都可以随意经过。

这样想到用 \(\text{Kruskal}\) 重构树建立连通性,越往上 \(C\) 越大,在树上判断。

答案一定是由一个节点向根节点方向走,到达 \(u\) 后将 \(u\) 的所有子树遍历后在向上走。

\(dp_{i}\) 表示从 \(i\) 子树中一点走到 \(i\) 所需要的最小初始钱数,\(S_i\) 表示 \(i\) 为根的子树的 \(B\) 之和,则根据儿子们如下转移:

  • 如果自己是叶子节点,则 \(dp_{i}=C_i\)
  • 如果不是,则 \(dp_{i}=\min_v\{\max(dp_i,C_i-S_v)\}\)
#define Maxn 100005
int n,m,tot,rt;
int bel[Maxn],A[Maxn],B[Maxn],C[Maxn],fa[Maxn];
int hea[Maxn],nex[Maxn<<1],ver[Maxn<<1];
ll dp[Maxn],sum[Maxn];
vector<int> g[Maxn];
struct Point
{
	int num,a,b,c;
	Point(int _num=0,int _a=0,int _b=0,int _c=0):num(_num),a(_a),b(_b),c(_c){}
	bool friend operator < (Point x,Point y)
		{ return (x.c!=y.c)?x.c<y.c:x.num<y.num; }
}d[Maxn];
int Find(int x){ return (fa[x]==x)?x:(fa[x]=Find(fa[x])); }
inline void add(int x,int y){ ver[++tot]=y,nex[tot]=hea[x],hea[x]=tot; }
void dfs(int x)
{
	if(!g[x].size()) { sum[x]=B[x],dp[x]=C[x]; return; }
	sum[x]=B[x],dp[x]=infll;
	for(int v:g[x])
		dfs(v),sum[x]+=sum[v],
		dp[x]=min(dp[x],max(dp[v],C[x]-sum[v]));
}
int main()
{
	n=rd(),m=rd();
	for(int i=1;i<=n;i++)
		A[i]=rd(),B[i]=rd(),C[i]=max(A[i]-B[i],0),
		d[i]=Point(i,A[i],B[i],C[i]),fa[i]=i;
	for(int i=1,x,y;i<=m;i++) x=rd(),y=rd(),add(x,y),add(y,x);
	sort(d+1,d+n+1);
	for(int p=1,x,y;p<=n;p++)
	{
		x=d[p].num;
		for(int i=hea[x];i;i=nex[i])
		{
			y=Find(ver[i]);
			if(x!=y && (C[y]<C[x] || (C[y]==C[x] && y<x)))
				g[x].pb(y),fa[y]=x;
		}
	}
	rt=d[n].num,dfs(rt);
	printf("%lld\n",dp[rt]+sum[rt]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值