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


[AHOI2014/JSOI2014]支线剧情

题目链接:luogu P4043

题目大意

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

思路

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

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

代码

#include
#include
#include
#include
#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  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;
}