【ybt金牌导航3-2-2】【luogu SP4063】【POJ 1149】【luogu P4638】卖猪问题 / Sell Pigs / PIGS / 银行家

卖猪问题 / Sell Pigs / PIGS / 银行家

题目链接:ybt金牌导航3-2-2 / luogu SP4063 / POJ 1149 / luogu P4638

题目大意

有一些位置有一定数量物品,依次会来人,人可以打开一些位置,拿走小于等于它需要数量的物品。
你可以在人打开位置时把这些打开了的位置里的东西转移到别的打开的位置中,每个位置可以放无限个东西。
然后人走了之后位置会被关上,问你最后所有人最多能总共拿走多少东西。

思路

容易想到可以搞网络流,但具体是如何建图:

首先,我们考虑人可以买的物品数,那我们把人看做节点,然后人连向汇点,流量就是那个人能买的数量。
接着我们来看,它说物品可以随着人打开传递,那我们总要看第一个打开这个位置的人吧。
那我们就源点连边想第一个打开这个位置的人,流量就是这个位置的物品数量。(我这两将连向同一个人的多个线合并了,合并就是流量相加)

那如何实现传递呢?
如果这个位置不是第一个被打开,那我们就可以从它前面转到的地方转过来,那我们记录上一个访问这个位置的人,它连向你现在的人,流量就是无限大。(因为位置可以放无限多个东西)

然后跑最大流就好了。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

struct node {
	int x, to, nxt, op;
}e[(701 + 701 * 701 * 2) * 2];
int n, m, tot_num, x, le[701], lee[701];
int pig[3001], S, T, dis[701], KK;
int keyy[701][3001], lst[3001];
queue <int> q;

void add(int x, int y, int z) {
	e[++KK] = (node){z, y, le[x], KK + 1}; le[x] = KK;
	e[++KK] = (node){0, x, le[y], KK - 1}; le[y] = KK;
}

bool bfs() {
	for (int i = 1; i <= T; i++) {
		dis[i] = -1;
		lee[i] = le[i];
	}
	while (!q.empty()) q.pop();
	
	q.push(S);
	dis[S] = 0;
	while (!q.empty()) {
		int now = q.front();
		q.pop();
		
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].x > 0 && dis[e[i].to] == -1) {
				dis[e[i].to] = dis[now] + 1;
				if (e[i].to == T) return 1;
				q.push(e[i].to);
			}
	}
	
	return 0;
}

int dfs(int now, int sum) {
	if (now == T) return sum;
	
	int go = 0;
	for (int &i = lee[now]; i; i = e[i].nxt)
		if (e[i].x > 0 && dis[e[i].to] == dis[now] + 1) {
			int this_go = dfs(e[i].to, min(sum - go, e[i].x));
			if (this_go) {
				e[i].x -= this_go;
				e[e[i].op].x += this_go;
				go += this_go;
				if (go == sum) return go;
			}
		}
	
	if (go < sum) dis[now] = -1;
	
	return go;
}

int dinic() {
	int re = 0;
	while (bfs())
		re += dfs(S, INF);
	return re;
}

int main() {
	scanf("%d %d", &m, &n);
	for (int i = 1; i <= m; i++)
		scanf("%d", &pig[i]);
	
	S = n + 1;
	T = n + 2;
	tot_num = T;
	
	for (int i = 1; i <= n; i++) {
		scanf("%d", &keyy[i][0]);
		int newpig = 0;
		for (int j = 1; j <= keyy[i][0]; j++) {
			scanf("%d", &keyy[i][j]);
			if (!lst[keyy[i][j]]) {
				lst[keyy[i][j]] = i;
				newpig += pig[keyy[i][j]];//这个位置是第一次被打开
			}
			else {
				add(lst[keyy[i][j]], i, INF);//不止一次被打开,上一次打开它的人连向现在的人,流量无限(其实就是把那些物品给转移)
				lst[keyy[i][j]] = i;
			}
		}
		if (newpig) add(S, i, newpig);//源点连向它,流量是第一次被大概的物品总数
		scanf("%d", &x);
		add(i, T, x);//它连向汇点,流量是它能拿走的物品数
	}
	
	printf("%d", dinic());//直接最大流
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值