1. 程式人生 > 其它 >【luogu P4043】[AHOI2014/JSOI2014]支線劇情(模板)(有源匯點上下界最小費用可行流)

【luogu P4043】[AHOI2014/JSOI2014]支線劇情(模板)(有源匯點上下界最小費用可行流)

[AHOI2014/JSOI2014]支線劇情

題目連結:luogu P4043

題目大意

有一個圖,然後每條邊有費用。
然後你要選擇一些從 1 出發的路徑使得每條邊至少被經過一次。
求最小費用。

思路

考慮如何網路流建圖,然後你發現路徑就是流量,這個至少經過一次其實可以看做是一個上下界(上界 \(\inf\) 下界為 \(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;
}