【luogu P4043】[AHOI2014/JSOI2014]支线剧情(模板)(有源汇点上下界最小费用可行流)

[AHOI2014/JSOI2014]支线剧情

题目链接:luogu P4043

题目大意

有一个图,然后每条边有费用。
然后你要选择一些从 1 出发的路径使得每条边至少被经过一次。
求最小费用。

思路

考虑如何网络流建图,然后你发现路径就是流量,这个至少经过一次其实可以看做是一个上下界(上界 inf ⁡ \inf inf 下界为 1 1 1).
然后每条边的费用的话其实就是最小费用。
所以其实就是有源汇点上下界最小费用可行流。

那接着你考虑怎么做。
然后你会发现,其实你好像就在有源汇上下界可行流的基础上,把你原图的边加上费用,然后跑的是费用流即可。

代码

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

using namespace std;

const int N = 300 + 10;
const int M = 50 + 10;
struct node {
	int x, val, to, nxt, op;
}e[N * M * 2 + N + N];
int n, m, S, T, le[N + 10], tot, KK, disum;
int ru[N + 10], chu[N + 10], s1, s2, t1, t2;
int dis[N + 10], lee[N + 10], deg[N + 10];
bool in[N + 10];

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

bool SPFA() {
	memset(deg, 0x7f, sizeof(deg));
	memset(dis, 0x7f, sizeof(dis));
	memcpy(lee, le, sizeof(lee));
	deg[S] = 0; dis[S] = 0;
	queue <int> q; q.push(S); in[S] = 1;
	while (!q.empty()) {
		int now = q.front(); q.pop();
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].x && dis[e[i].to] > dis[now] + e[i].val) {
				dis[e[i].to] = dis[now] + e[i].val; deg[e[i].to] = deg[now] + 1;
				if (!in[e[i].to]) {
					in[e[i].to] = 1; q.push(e[i].to);
				}
			}
		in[now] = 0;
	}
	return dis[T] != dis[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 && deg[e[i].to] == deg[now] + 1 && dis[e[i].to] == dis[now] + e[i].val) {
			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 (SPFA())
		re += dfs(S, INF) * dis[T];
	return re;
}

int main() {
	scanf("%d", &n);
	
	tot = n; s1 = 1; t1 = ++tot; s2 = ++tot; t2 = ++tot;
	for (int i = 1; i <= n; i++) {
		int k, b, t; scanf("%d", &k);
		for (int j = 1; j <= k; j++) {
			scanf("%d %d", &b, &t);
			Add(i, b, INF, t);
			chu[i] += 1; ru[b] += 1;
			disum += 1 * t;
		}
	}
	for (int i = 2; i <= n; i++) {
		Add(i, t1, INF, 0);
	}
	
	for (int i = 1; i <= n; i++) {
		if (ru[i] > chu[i]) Add(s2, i, ru[i] - chu[i], 0);
		if (chu[i] > ru[i]) Add(i, t2, chu[i] - ru[i], 0);
	}
	
	Add(t1, s1, INF, 0);
	S = s2; T = t2;
	printf("%d", disum + Dinic());
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值