AtCoder Regular Contest 098 F.Donation

传送门

首先,对于一个点i,进入这个点前必须大于等于Ai,每个点必须捐赠Bi
那么我们可以在每个点最后一次经过的时候再捐赠,这样显然更优

现在我们假设每个点都是最后一次经过的时候捐赠。现在我们把捐赠的钱排除在外,对于每个点来说,需要max(Ai-Bi, 0)才能经过这个点
显然,max(Ai-Bi, 0)越大的点需要尽快到达,不然后面钱越来越少,就搞不定了

如果遵循这个规律,优先走max(Ai-Bi, 0)大的点。对于每一点,步骤可以概括为走完所有的子图(剩一个不走),显然这个过程不需要耗费钱财(因为父节点max(Ai-Bi, 0)最大),最后走剩下的那个子图,也就是说讨论选哪个字图最后走比较合适

所以我们采用树dp,从上往下构建一个递减的树
(我觉得我还是很难说明白,如果还不懂,可以看下代码或者官方题解)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <map>
#include <vector>
#include <set>
typedef long long ll;
using namespace std;
const int N = 1e5+5;

int n, m;
vector<int> E[N];
int A[N], B[N], pos[N], vis[N], fa[N];
long long sum[N], add[N];
inline bool cmp(int x, int y) { return A[x] < A[y]; }
inline int find(int x) { return (x == fa[x]) ? x : (fa[x] = find(fa[x])); }
int main() {
    while(~scanf("%d %d", &n, &m)) {
        for(int i = 1; i <= n; ++i) E[i].clear();
        for(int i = 1; i <= n; ++i) {
            pos[i] = i; vis[i] = 0; fa[i] = i;
            scanf("%d %d", &A[i], &B[i]);
            A[i] = max(0, A[i] - B[i]);
            add[i] = A[i];
            sum[i] = B[i];
        }

        sort(pos+1, pos+n+1, cmp);
        for(int i = 0; i < m; ++i) {
            int a, b; scanf("%d %d", &a, &b);
            E[a].push_back(b);
            E[b].push_back(a);
        }

        for(int i = 1; i <= n; ++i) {
            vis[pos[i]] = 1;
            for(int j = 0; j < E[pos[i]].size(); ++j) {
                int tt = E[pos[i]][j];
                if(vis[tt]) {
                    int f1 = find(pos[i]); int f2 = find(tt);
                    if(f1 != f2) {
                        sum[f1] += sum[f2];
                        fa[f2] = f1; 
                        add[f1] = min(add[f1], add[f2] + max(0ll, A[f1] - add[f2] - sum[f2])); 
                    }
                }
            }
        }
        printf("%lld
", sum[pos[n]] + add[pos[n]]);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/Basasuya/p/9176539.html