ARC098F Donation

给定 \(n\) 个点 \(m\) 条边的无向简单连通图和 \(n\) 个正整数 \(A_i,B_i\)

猫猫可以空降在任意一个点,猫猫经过点 \(u\) 时至少需要拥有 \(A_u\) 元,打卡一次需要花费 \(B_u\) 元。

求在每个点都打卡一次所需的最小初始钱数。

\(n,m\le 10^5,A_i,B_i\le 10^9\)


显然若要重复经过某个点,则在最后经过的这一次打卡是最优的。

先令 \(A_i=\max(A_i-B_i,0)\),表示走出时的最小钱数。要求即为在 \(u\) 点的任意时刻钱数 \(\ge A_u\)

\((u,v)\) 这条边的边权为 \(\max(A_u,A_v)\),猫猫走的边一定是最小生成树上的边。

建出 Kruskal 重构树,考虑树形 dp。设 \(f_u\) 表示 \(u\) 子树内的点都打上卡所需的最少初始钱数,\(s_u\) 表示 \(u\) 子树内 \(B_i\) 之和。

\(u\) 是叶节点,则 \(f_u=A_u+B_u\)

\(u\) 是非叶节点,显然是先处理一棵子树,然后在 \(u\) 打卡,再处理另一棵子树:

\[f_u=\min\{s_{ls_u}+\max\{A_u,f_{rs_u}\},s_{rs_u}+\max\{A_u,f_{ls_u}\}\} \]

时间复杂度 \(O(n+m\log m)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 222222;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
int n, m, k, A[N], B[N], fa[N], ch[N][2];
LL f[N], s[N];
struct Edge {
	int u, v, w;
	bool operator < (const Edge &o) const {return w < o.w;}
} e[N];
int getfa(int x){return x == fa[x] ? x : fa[x] = getfa(fa[x]);}
int main(){
	read(n); read(m); k = n;
	for(int i = 1;i <= n;++ i){
		read(A[i]); read(B[i]); fa[i] = i;
		A[i] = max(A[i] - B[i], 0);
		f[i] = A[i] + B[i]; s[i] = B[i];
	} for(int i = 1, u, v;i <= m;++ i){
		read(u); read(v);
		e[i].u = u; e[i].v = v;
		e[i].w = max(A[u], A[v]);
	} sort(e + 1, e + m + 1);
	for(int i = 1;i <= m;++ i){
		int u = getfa(e[i].u), v = getfa(e[i].v);
		if(u != v){ ++ k;
			fa[u] = fa[v] = fa[k] = k;
			A[k] = max(A[u], A[v]); s[k] = s[u] + s[v];
			f[k] = min(s[u] + max((LL)A[k], f[v]), s[v] + max((LL)A[k], f[u]));
		}
	} printf("%lld\n", f[k]);
}
posted @ 2021-03-16 19:05  mizu164  阅读(71)  评论(0编辑  收藏  举报