题解 DTOJ #1544. 还债(debt)

欢迎访问 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值