[ARC098F] Donation

AT传送门/LG传送门

从这题学了 Kruskal 重构树。
如果说在一个点捐了钱,后续不可能再路过这个点。原因是两次路过同一个点,第二次捐钱一定比第一次优,因为两次路过该点之间走的路可用的钱更多。
那么考虑倒着来进行这个过程:假定已经选定了终点\(t\),从\(t\)出发第一次到达某个点\(i\)时,必须满足手里的钱\(x\geq max(a_i-b_i,0)\),路过这个点之后,\(x\)会增加\(b_i\),后面再路过这个点就不会有收益。
从当前已经走过的点出发,找一个未走过的\(a_i-b_i\)最小的点,一定不劣,因为他的限制更容易满足,拿了他的收益以后更有可能走其它点。
考虑如下过程:
维护一个已走过的点集\(S\)和当前手里的钱数\(x\),初始时\(S=\{t\}\)\(x=0\)
找到一个不在\(S\)中的点\(i\),满足\(a_i-b_i\)最小,如果\(x\geq a_i-b_i\),令\(x\)加上\(b_i\),否则令答案增加\(a_i-b_i-x\),并将\(x\)设为\(a_i\),最后将\(i\)加入\(S\)
然而这个过程需要枚举终点,是不优秀的。
发现这个过程类似于 Prim 。
我们设\(c_i=a_i-b_i\),在原图中的\((u,v)\)连一条边权为\(max(c_u,v_v)\)的边,不难发现最后的路径一定在最小生成树上。然而最小生成树的性质仍然不够好。
于是考虑建立 Kruskal 重构树。这样所有的终点必定是叶子节点,从叶子节点往上走,其祖先的点权是随着深度减少而增大的。这意味着我们走过一个祖先\(f\)\(f\)的后代必定都走过了。当我们来到\(f\),如果当前\(x\geq c_f\)\(f\)的另一个子树内的点也必然能够走到,因为\(f\)子树内的所有\(c\)都不大于\(c_f\),且我们的钱数是一个单调不降的过程。
我们把上面的做法拿到 Kruskal 重构树上做。初始钱数\(x=0\),从叶子节点往上走,来到节点\(i\),如果\(x\geq c_i\),令\(x\)加上\(i\)的另一个子树内的\(b\)之和;否则令答案增加\(c_i-x\),并将\(x\)设为\(c_i\)后加上\(i\)另一个子树内\(b\)之和。一直到根为止。
这样仍然需要枚举叶子,不过所有路径都是从叶子到根,要是能从根往下扫直到每个叶子统计答案就是能接受的。
我们记\(sum_b(f)\)表示以\(f\)为根的子树内的\(b\)之和。按照上述过程,一个\(x\)要想成为合法答案,需要满足:
对于某个叶子到根的路径上所有\(u\)\(v\)的父亲,有:\(x+sum_b(v)\geq c_u\),也即\(x\geq c_u-sum_b(v)\)
一次 DFS 从根向下走一遍并在叶子统计答案即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pp pop_back
#define pb push_back
#define ins insert
#define lowbit(x) x & -x

const int N = 6e5 + 10;
const int mod = 998244353;
const ll INF = 1e18;

int read(){int x; scanf("%d", &x); return x; }
ll readll(){ll x; scanf("%lld", &x); return x; }

int n, m;
int a[N], lc[N], rc[N], fa[N];
ll b[N], c[N], res = 1e18, Sum = 0;
struct node{
	int x, y;
	ll w;
	bool friend operator < (node a, node b){
		return a.w < b.w;
	}
}edge[N];

void dfs(int x, ll k){
	if(x <= n) return res = min(res, max(1ll * c[x], k)), void();
	if(lc[x]) dfs(lc[x], max(k, c[x] - b[lc[x]]));
	if(rc[x]) dfs(rc[x], max(k, c[x] - b[rc[x]]));
}

int get(int x){
	if(fa[x] == x) return x; 
	else return fa[x] = get(fa[x]);
}

signed main(){
	n = read(), m = read();
	for(int i = 1; i <= n; i++) a[i] = read(), b[i] = read(), Sum += b[i];
	for(int i = 1; i <= n; i++) c[i] = max(a[i] - b[i], 0ll);
	for(int i = 1; i <= m; i++){
		int u = read(), v = read();
		edge[i] = {u, v, max(c[u], c[v])};
	}
	sort(edge + 1, edge + m + 1);
	for(int i = 1; i <= 2 * n; i++) fa[i] = i;
	int tt = n;
	for(int i = 1; i <= m; i++){
		int x = edge[i].x, y = edge[i].y;
		ll w = edge[i].w;
		int fx = get(x), fy = get(y);
		if(fx != fy){
			tt++;
			fa[fx] = fa[fy] = tt;
			lc[tt] = fx, rc[tt] = fy;
			c[tt] = w;
			b[tt] = b[fx] + b[fy];
		}
	}
	dfs(n * 2 - 1, 0);
	cout << res + Sum;
	return 0;
}
posted @ 2025-07-25 14:34  Lordreamland  阅读(12)  评论(3)    收藏  举报