欢迎访问 My Luogu Space。
【题目大意】
有 n n n 个人,每个人一定且只欠另外一个人一些钱。每个人收到钱一定会还清自己的债务,但必须一次性还清。
现在每个人都没有钱,你要用送给他们最少的钱,让他们所有人都还清债务。
【题解】
首先考虑到每个人一定欠另外一个人一些钱,所以我们将欠钱的人连向被欠钱的人,会得到一张有向图,且这个有向图一定是由一些连贯的环以及一些指向环的链组成。
对于这些指向环的链,我们可以用拓扑算出答案。
接下来我们需要找出这些环当中需要给他的钱数最少的那个人(可能给他的钱数为零),然后给他钱,再从他将这个环做完。
这样得到的答案就是最优的答案,因为不管从哪里开始,每个人(不包括你送他们的)得到的钱一定是一个定值。
【代码】
// output format !!!
// long long !!!
#include <bits/stdc++.h>
typedef long long LL;
using namespace std;
const int MAXN = 200000+10;
const int INF = 0x3f3f3f3f;
int n, A[MAXN], B[MAXN], in[MAXN], f[MAXN];
int H[MAXN], rh, ri, tot;
LL num[MAXN], ans;
void dfs(int x){
if(f[x]) return;
if(num[x] < B[x]) return;
num[A[x]] += B[x], --tot;
f[x] = 1, --in[A[x]], dfs(A[x]);
}
void work1(){
for(int i=1; i<=n; ++i)
if(!f[i] && !in[i]) H[++rh] = i;
while(rh){
for(int i=1; i<=rh; ++i){
int x = H[i];
if(num[x] < B[x]) ans += B[x]-num[x];
num[A[x]] += B[x], --tot, f[x] = 1;
if(!f[A[x]] && !--in[A[x]]) H[++ri] = A[x];
}
rh = ri, ri = 0;
}
if(!tot) printf("%lld", ans), exit(0);
}
int main(){
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
scanf("%d", &n), tot = n;
for(int i=1; i<=n; ++i)
scanf("%d%d", A+i, B+i), ++in[A[i]];
work1();
for(int i=1; i<=n; ++i)
if(!f[i]) dfs(i);
if(!tot) printf("%lld", ans), exit(0);
work1();
while(tot){
int Min = INF, id;
for(int i=1; i<=n; ++i)
if(!f[i] && B[i]-num[i]<Min){
Min = B[i]-num[i];
id = i;
}
num[A[id]] += B[id];
ans += Min, --tot, --in[A[id]];
f[id] = 1;
dfs(A[id]);
work1();
}
printf("%lld", ans);
return 0;
}