网络流 24 题

1、搭配飞行员

求二分图最大匹配,建立源点 S S S,汇点 T T T S S S 向正驾驶员连接容量 1 1 1 的边,副驾驶员向 T T T 连接容量为 1 1 1 的边,可以匹配的正副驾驶员之间连接容量为 1 1 1 的边,求最大流即为最大匹配。

set<int> li, ri;

int main() {

	Dinic<int> dn;
	int n, m; scanf("%d%d", &n, &m);
	int u, v, sp = n + 1, tp = sp + 1;
	dn.init(tp);
	while(scanf("%d%d", &u, &v) != EOF){

		dn.add(u, v, 1);
		li.insert(u), ri.insert(v);
	}
	for(auto x : li) dn.add(sp, x, 1);
	for(auto x : ri) dn.add(x, tp, 1);
	printf("%d\n", dn.solve(sp, tp));
    return 0;
}
2、太空飞行计划

求最大收益即为求最小花费,初始先加上所有收入,再求最小花费。根据冲突关系建边,每个实验 E i E_i Ei 向所需仪器编号连接容量为 i n f inf inf 的边,源点 S S S E i E_i Ei 连接容量为 p i p_i pi 的边,每种仪器 I i I_i Ii 向汇点 T T T 连接容量为 c i c_i ci 的边,则要么进行实验 E i E_i Ei,割去对应仪器集合的所有连向 T T T 的边(即花费),要么割去实验 E i E_i Ei 代表边。求最小割即为最小花费。

至于方案输出,再从源点 S S S 做一次 b f s bfs bfs,则某条边为割边当且仅当两端点一端可达且另一端不可达。

void print(int m){

	bfs();
	int f = 0;
	for(Edge &e : G[sp]){

		if(dis[e.v]){

			if(f) cout << " "; f = 1;
			cout << e.v;
		}
	}
	cout << endl;
	f = 0;
	for(Edge &e : G[tp]){

		if(!dis[tp] != !dis[e.v]){

			if(f) cout << " "; f = 1;
			cout << e.v - m;
		}
	}
	cout << endl;
}

int main() {

	Dinic<int> dn;
	int m, n; cin >> m >> n;
	int sp = m + n + 1, tp = sp + 1;
	dn.init(tp);
	int ret = 0;
	for(int i = 1; i <= m; ++i){

		while(!isdigit(cin.peek())) cin.get();
		string s; getline(cin, s);
		stringstream ss;
		ss << s;
		int x; ss >> x;
		ret += x;
		dn.add(sp, i, x);
		while(ss >> x){

			dn.add(i, m + x, inf);
		}
	}
	for(int i = 1; i <= n; ++i){

		int tmp; cin >> tmp;
		dn.add(m + i, tp, tmp);
	}
	ret -= dn.solve(sp, tp);
	dn.print(m);
	cout << ret;
    return 0;
}
3、最小路径覆盖

有向图最小路径覆盖,将每个点 u u u 拆成 u u u u ′ u' u,对边 ( u , v ) (u,v) (u,v),连接 u u u v ′ v' v,则最小路径覆盖数 = = = 点数 − - 最大匹配。求最大流, d f s dfs dfs 输出方案。

void print(int m){

	for(int i = 1; i <= n; ++i) dis[i] = 0;
	for(int i = 1; i <= m; ++i){

		int p = i; ans.clear();
		while(!dis[p]){

			dis[p] = 1, ans.pb(p);
			for(Edge &e : G[p]){

				if(e.v == sp || !G[e.v][e.rev].cap) continue;
				p = e.v - m;
			}
		}
		if(!sz(ans)) continue;
		for(int x : ans) printf("%d ", x);
		printf("\n");
	}
}

int main() {

	Dinic<int> dn;
	int n, m; scanf("%d%d", &n, &m);
	int sp = n * 2 + 1, tp = sp + 1;
	dn.init(tp);
	while(m--){

		int u, v; scanf("%d%d", &u, &v);
		dn.add(u, n + v, 1);
	}
	for(int i = 1; i <= n; ++i) dn.add(sp, i, 1), dn.add(n + i, tp, 1);
	int ret = n - dn.solve(sp, tp);
	dn.print(n);
	printf("%d\n", ret);
    return 0;
}
4、魔术球

这题有贪心的解法,这里说一下个人的做法。
先预处理出所有数字的后继。二分答案 a n s ans ans,问题转化为判定能否用 n n n 条链覆盖 a n s ans ans 个球。对每个球 u u u 拆点 u u u u ′ u' u u u u u ′ u' u 连接容量为 1 1 1、费用为 − 1 -1 1 的边, S S S 向所有球连容量 1 1 1、费用 0 0 0 的边,所有球向后继连边容量 1 1 1、费用 0 0 0 的边,所有球向 T T T 连容量 1 1 1、费用 0 0 0 的边,求最小费用最大流判定。

u p d a t e update update: 这题其实是最小路径覆盖,无需费用流,只需要判定是否最小路径覆盖数 <= n n n,并且也可以不二分,枚举答案每次在残留网络上跑就行。

void dfs(int u){

	if(u == tp) return;
	if(u != sp) ans.pb(u);
	for(auto e : G[u]){

		if(e.cot < 0 || e.v < u && G[e.v][e.rev].cap){

			--G[e.v][e.rev].cap;
			dfs(e.v);
			break;
		}
	}
}
void print(int m, int ret){

	sp = G[sp][0].v, tp = G[tp][0].v;
	for(int i = 1; i <= m; ++i){

		ans.clear();
		dfs(sp); int f = 0;
		for(int j = 0; j < sz(ans); j += 2){

			if(f) cout << " "; f = 1;
			cout << ans[j];
		}
		cout << endl;
	}
}

Dinic<int> dn;
vector<int> G[maxn];
int m;

int judge(int mid){

	int sp = mid * 2 + 1, tp = sp + 1, st = tp + 1, tt = st + 1;
	dn.init(tt);
	dn.add(sp, st, m, 0);
	dn.add(tt, tp, m, 0);
	for(int i = 1; i <= mid; ++i){

		for(auto x : G[i]){

			if(x > mid) break;
			dn.add(mid + i, x, 1, 0);
		}
		dn.add(i, mid + i, 1, -1);
		dn.add(st, i, 1, 0);
		dn.add(mid + i, tt, 1, 0);
	}
	return dn.solve(sp, tp).second == -mid;
}

void init(){

	for(int i = 1; i <= 1567; ++i){

		for(int j = i + 1; j <= 1567; ++j){

			int x = sqrt(i + j + 0.5);
			if(x * x == i + j) G[i].pb(j);
		}
	}
}

int main() {

	init();
	scanf("%d", &m);
	int l = 1, r = 1567, mid, ret;
	while(l <= r){

		mid = gmid;
		if(judge(mid)) l = mid + 1, ret = mid;
		else r = mid - 1;
	}
	printf("%d\n", ret);
	judge(ret);
	dn.print(m, ret);
    return 0;
}
5、圆桌聚餐

S S S 向第 i i i 个单位连容量为 r i r_i ri 的边,第 i i i 张餐桌向 T T T 连容量为 c i c_i ci 的边,所有单位分别向每张餐桌连容量为 1 1 1 的边,求最大流并输出方案。

void print(int m){

	for(int i = 1; i <= m; ++i){

		for(auto &e : G[i]){

			if(e.v == sp || e.cap) continue;
			printf("%d ", e.v - m);
		}
		printf("\n");
	}
}

int main() {

	Dinic<int> dn;
	int m, n; scanf("%d%d", &m, &n);
	int sp = m + n + 1, tp = sp + 1;
	dn.init(tp);
	int sum = 0;
	for(int i = 1; i <= m; ++i){

		int tmp; scanf("%d", &tmp);
		dn.add(sp, i, tmp);
		sum += tmp;
	}
	for(int i = 1; i <= n; ++i){

		int tmp; scanf("%d", &tmp);
		dn.add(m + i, tp, tmp);
	}
	for(int i = 1; i <= m; ++i){

		for(int j = 1; j <= n; ++j) dn.add(i, m + j, 1);
	}
	if(dn.solve(sp, tp) != sum) printf("0\n");
	else{

		printf("1\n");
		dn.print(m);
	}
    return 0;
}
6、最长递增子序列

d p dp dp 求出以 i i i 结尾的最长递增子序列,特判 l i s = 1 lis=1 lis=1 S S S d p [ i ] = 1 dp[i]=1 dp[i]=1 的点连边, d p [ i ] = l i s dp[i]=lis dp[i]=lis的向 T T T连边,对于 x j x_j xj,向后继状态 x i x_i xi 连边,以上边容量皆为 1 1 1。求最大流则为第二个询问的答案。

对于第三个询问, S S S x 1 x_1 x1 连容量为 i n f inf inf 的边,若 d p [ n ] = l i s dp[n]=lis dp[n]=lis x n x_n xn T T T 连容量为 i n f inf inf 的边,再求一次最大流。

int a[maxn], dp[maxn], n;

int main() {

	Dinic<int> dn;
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	int lis = 0;
	for(int i = 1; i <= n; ++i){

		dp[i] = 1;
		for(int j = 1; j < i; ++j){

			if(a[j] <= a[i]) dp[i] = max(dp[i], dp[j] + 1);
		}
		lis = max(lis, dp[i]);
	}
	printf("%d\n", lis);
	if(lis == 1){

		printf("%d\n%d\n", n, n);
		return 0;
	}
	int sp = n + 1, tp = sp + 1;
	dn.init(tp);
	for(int i = 1; i <= n; ++i){

		if(dp[i] == 1) dn.add(sp, i, 1);
		else if(dp[i] == lis) dn.add(i, tp, 1);
		for(int j = 1; j < i; ++j){

			if(a[j] <= a[i] && dp[j] + 1 == dp[i]) dn.add(j, i, 1);
		}
	}
	int ret = dn.solve(sp, tp);
	printf("%d\n", ret);
	dn.add(sp, 1, inf);
	if(dp[n] == lis) dn.add(n, tp, inf);
	ret += dn.solve(sp, tp);
	printf("%d\n", ret);
    return 0;
}
7、试题库

S S S 向所有题目连容量为 1 1 1 的边,所有题目类型向 T T T 连容量为所需题数的边,对于每道题目,向可以归类的类型连容量为 1 1 1 的边。求最大流并输出方案。

void print(int n, int k){

	for(int i = n + 1; i <= n + k; ++i){

		printf("%d:", i - n);
		for(auto &e : G[i]){

			if(e.v == tp || !e.cap) continue;
			printf(" %d", e.v);
		}
		printf("\n");
	}
}

int main() {

	Dinic<int> dn;
	int k, n; scanf("%d%d", &k, &n);
	int sp = k + n + 1, tp = sp + 1, sum = 0;
	dn.init(tp);
	for(int i = 1; i <= k; ++i){

		int tmp; scanf("%d", &tmp);
		dn.add(n + i, tp, tmp);
		sum += tmp;
	}
	for(int i = 1; i <= n; ++i){

		int p; scanf("%d", &p);
		while(p--){

			int u; scanf("%d", &u);
			dn.add(i, n + u, 1);
		}
		dn.add(sp, i, 1);
	}
	if(dn.solve(sp, tp) != sum) printf("No Solution!\n");
	else dn.print(n, k);
    return 0;
}
8、方格取数

先将所有数字相加,转化为求割去的最小代价。将方格黑白染色,利用黑白方格冲突关系建边,求最小割。

const int xp[] = { 0, 0, -1, 1 };
const int yp[] = { -1, 1, 0, 0 };
int n, m, a[maxn][maxn];

int check(int x, int y){

	return x >= 1 && x <= n && y >= 1 && y <= m;
}

int getId(int x, int y){

	return (x - 1) * m + y;
}

int getCol(int x, int y){

	return (x & 1) && (y & 1) || (~x & 1) && (~y & 1);
}

int main() {

	Dinic<int> dn;
	scanf("%d%d", &n, &m);
	int sp = n * m + 1, tp = sp + 1;
	dn.init(tp);
	int ret = 0;
	for(int i = 1; i <= n; ++i){

		for(int j = 1; j <= m; ++j){

			scanf("%d", &a[i][j]);
			ret += a[i][j];
		}
	}
	for(int i = 1; i <= n; ++i){

		for(int j = 1; j <= m; ++j){

			int id = getId(i, j);
			if(getCol(i, j)){

				dn.add(id, tp, a[i][j]);
				continue;
			}
			dn.add(sp, id, a[i][j]);
			for(int k = 0; k < 4; ++k){

				int x = i + xp[k], y = j + yp[k];
				if(check(x, y)){

					dn.add(id, getId(x, y), inf);
				}
			}
		}
	}
	ret -= dn.solve(sp, tp);
	printf("%d\n", ret);
    return 0;
}
9、餐巾计划

考虑到每条餐巾有新、旧两种状态,对每条餐巾建立两个点 u u u u ′ u' u,分别代表旧餐巾和新餐巾。 S S S 向所有 u ′ u' u 连边 ( i n f , P ) (inf, P) (inf,P) 表示第 i i i 天可选择以单位价格为 P P P 购入新餐巾, u ′ u' u T T T 连边 ( r i , 0 ) (r_i, 0) (ri,0),表示第 i i i 天需要 r i r_i ri 条新餐巾, S S S u u u ( r i , 0 ) (r_i, 0) (ri,0) 的边,表示第 i i i 天产生 r i r_i ri 条旧餐巾, u i u_i ui u i + 1 u_{i+1} ui+1 连边 ( i n f , 0 ) (inf, 0) (inf,0) 表示旧餐巾可以留到下一天, u i u_i ui u i + M ′ u'_{i+M} ui+M 连边 ( i n f , F ) (inf, F) (inf,F) 表示快洗 M M M 天后旧餐巾变新餐巾,慢洗同理。最后求最小费用流即可。

int main() {

	Dinic<int> dn;
	int n, P, M, F, N, S;
	scanf("%d%d%d%d%d%d", &n, &P, &M, &F, &N, &S);
	int sp = n * 2 + 1, tp = sp + 1;
	dn.init(tp);
	for(int i = 1; i <= n; ++i){

		int tmp; scanf("%d", &tmp);
		dn.add(sp, i, tmp, 0);
		dn.add(sp, n + i, inf, P);
		dn.add(n + i, tp, tmp, 0);
	}
	for(int i = 1; i <= n; ++i){

		if(i < n) dn.add(i, i + 1, inf, 0);
		if(i + M <= n) dn.add(i, n + i + M, inf, F);
		if(i + N <= n) dn.add(i, n + i + N, inf, S);
	}
	int ret = dn.solve(sp, tp).second;
	printf("%d\n", ret);
    return 0;
}
10、软件补丁

网络流貌似很不可做啊。比较明显的是可以状压跑最短路。(事实上不必显式建图)

vector<pii> G[maxn];
int dis[maxn];
int n, m;

void dij(int s, int n){

	priority_queue<pii, vector<pii>, greater<pii> > q;
	for(int i = 0; i < n; ++i) dis[i] = inf;
	dis[s] = 0, q.push({dis[s], s});
	while(!q.empty()){

		int u = q.top().second, d = q.top().first; q.pop();
		for(int i = 0; i < sz(G[u]); ++i){

			int v = G[u][i].second, w = G[u][i].first;
			if(dis[v] > dis[u] + w){

				dis[v] = dis[u] + w;
				q.push({dis[v], v});
			}
		}
	}
}

int main() {

	scanf("%d%d", &n, &m);
	int lim = 1 << n, msk = lim - 1;
	while(m--){

		int w; char s1[25], s2[25]; scanf("%d%s%s", &w, s1, s2);
		int b1 = 0, b2 = 0, f1 = 0, f2 = 0;
		for(int i = 0; s1[i]; ++i){

			if(s1[i] == '+') b1 |= 1 << i;
			else if(s1[i] == '-') b2 |= 1 << i;
			if(s2[i] == '-') f1 |= 1 << i;
			else if(s2[i] == '+') f2 |= 1 << i;
		}
		f1 = ~f1 & msk;
		for(int i = 0; i < lim; ++i){

			if((i & b1) == b1 && ((~i & msk) & b2) == b2){

				G[i].pb({w, i & f1 | f2});
			}
		}
	}
	dij(lim - 1, lim);
	printf("%d\n", dis[0] != inf ? dis[0] : 0);
    return 0;
}
11、数字梯形

有三问,区别在于是否有限制点、边只能经过一次。对于边经过一次,可以直接连边容量为 1 1 1;对于点,拆点 u u u u ′ u' u,之间连接容量为 1 1 1 的边,则点只能经过一次。最后跑最大费用最大流。

Dinic<int> dn;
int a[maxn][maxn], id[maxn][maxn];
int m, n, tot;

int solve(int vcap, int ecap){

	int sp = tot * 2 + 1, tp = sp + 1;
	dn.init(tp);
	for(int i = 1; i <= m; ++i) dn.add(sp, id[1][i], 1, 0);
	for(int i = 1; i < m + n; ++i) dn.add(tot + id[n][i], tp, inf, 0);
	for(int i = 1; i <= n; ++i){

		for(int j = 1; j < m + i; ++j){

			if(i < n) dn.add(tot + id[i][j], id[i + 1][j], ecap, 0);
			if(i < n) dn.add(tot + id[i][j], id[i + 1][j + 1], ecap, 0);
			dn.add(id[i][j], tot + id[i][j], vcap, -a[i][j]);
		}
	}
	return -dn.solve(sp, tp).second;
}

int main() {

	scanf("%d%d", &m, &n);
	for(int i = 1; i <= n; ++i){

		for(int j = 1; j < m + i; ++j){

			scanf("%d", &a[i][j]);
			id[i][j] = ++tot;
		}
	}
	printf("%d\n", solve(1, 1));
	printf("%d\n", solve(inf, 1));
	printf("%d\n", solve(inf, inf));
    return 0;
}
12、运输问题

S S S 向所有仓库连边 ( a i , 0 ) (a_i, 0) (ai,0),所有商店向 T T T 连边 ( b i , 0 ) (b_i, 0) (bi,0),仓库和商店之间连边 ( i n f , c i j ) (inf, c_{ij}) (inf,cij),分别求最小费用最大流和最大费用最大流。

Dinic<int> dn;
int a[maxn], b[maxn], c[maxn][maxn];
int m, n;

int solve(int f){

	int sp = m + n + 1, tp = sp + 1;
	dn.init(tp);
	for(int i = 1; i <= m; ++i) dn.add(sp, i, a[i], 0);
	for(int i = 1; i <= n; ++i) dn.add(m + i, tp, b[i], 0);
	for(int i = 1; i <= m; ++i){

		for(int j = 1; j <= n; ++j){

			dn.add(i, m + j, inf, f * c[i][j]);
		}
	}
	return f * dn.solve(sp, tp).second;
}

int main() {

	scanf("%d%d", &m, &n);
	for(int i = 1; i <= m; ++i) scanf("%d", &a[i]);
	for(int i = 1; i <= n; ++i) scanf("%d", &b[i]);
	for(int i = 1; i <= m; ++i)
		for(int j = 1; j <= n; ++j) scanf("%d", &c[i][j]);
	printf("%d\n", solve(1));
	printf("%d\n", solve(-1));
    return 0;
}
13、分配问题

二分图最佳匹配,除了 k m km km 算法以外,网络流做法同上题,将边容量改成 1 1 1 即可。

Dinic<int> dn;
int c[maxn][maxn];
int n;

int solve(int f){

	int sp = n * 2 + 1, tp = sp + 1;
	dn.init(tp);
	for(int i = 1; i <= n; ++i) dn.add(sp, i, 1, 0);
	for(int i = 1; i <= n; ++i) dn.add(n + i, tp, 1, 0);
	for(int i = 1; i <= n; ++i){

		for(int j = 1; j <= n; ++j){

			dn.add(i, n + j, inf, f * c[i][j]);
		}
	}
	return f * dn.solve(sp, tp).second;
}

int main() {

	scanf("%d", &n);
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j) scanf("%d", &c[i][j]);
	printf("%d\n", solve(1));
	printf("%d\n", solve(-1));
    return 0;
}
14、负载平衡

除了贪心做法外,网络流做法如下。
显然可以得到最终每个仓库的库存数,每个仓库拆点 u u u u ′ u' u S S S 向所有需要减少库存的仓库 u u u 连边,容量为需要减少的库存数,所有需要增加库存的 u ′ u' u T T T 连边,容量为需要增加的库存数。对所有 u i u_i ui,向 u i − 1 u_{i-1} ui1 u i + 1 u_{i+1} ui+1 u i − 1 ′ u'_{i-1} ui1 u i + 1 ′ u'_{i+1} ui+1 连接 ( i n f , 1 ) (inf, 1) (inf,1) 的边,则最终满流则代表负载平衡,最小费用则为答案。

Dinic<int> dn;
int a[maxn];
int n;

int main() {

	scanf("%d", &n);
	for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	int sp = n * 2 + 1, tp = sp + 1;
	dn.init(tp);
	int sum = 0;
	for(int i = 1; i <= n; ++i) sum += a[i];
	sum /= n;
	for(int i = 1; i <= n; ++i){

		if(a[i] > sum) dn.add(sp, i, a[i] - sum, 0);
		else if(a[i] < sum) dn.add(n + i, tp, sum - a[i], 0);
		int l = i == 1 ? n : i - 1, r = i == n ? 1 : i + 1;
		dn.add(i, l, inf, 1), dn.add(i, n + l, inf, 1);
		dn.add(i, r, inf, 1), dn.add(i, n + r, inf, 1);
	}
	int ret = dn.solve(sp, tp).second;
	printf("%d\n", ret);
    return 0;
}
15、最长 k 可重区间集

离散化,对于区间 ( l i ,   r i ) (l_i,~r_i) (li, ri) l i l_i li r i r_i ri ( 1 , − z i ) (1, -z_i) (1,zi) 的边,按 x x x 轴正方向,每个点向下一点连 ( i n f , 0 ) (inf, 0) (inf,0) 的边, S S S 向最左端的点连 ( k , 0 ) (k, 0) (k,0) 的边,最右端的点向 T T T ( k , 0 ) (k, 0) (k,0) 的边。对于单次增广,数轴上每个点只会被经过一次,求最小费用最大流即为 k k k 可重。

Dinic<int> dn;
pii a[maxn];
int xi[maxn], wi[maxn];
int n, k, tot;

int main() {

	scanf("%d%d", &n, &k);
	for(int i = 1; i <= n; ++i){

		scanf("%d%d", &a[i].first, &a[i].second);
		if(a[i].first > a[i].second) swap(a[i].first, a[i].second);
		wi[i] = a[i].second - a[i].first;
		xi[++tot] = a[i].first, xi[++tot] = a[i].second;
	}
	sort(xi + 1, xi + 1 + tot);
	tot = unique(xi + 1, xi + 1 + tot) - xi - 1;
	for(int i = 1; i <= n; ++i){

		a[i].first = lower_bound(xi + 1, xi + 1 + tot, a[i].first) - xi;
		a[i].second = lower_bound(xi + 1, xi + 1 + tot, a[i].second) - xi;
	}
	int sp = tot + 1, tp = sp + 1;
	dn.init(tp);
	dn.add(sp, 1, k, 0), dn.add(tot, tp, k, 0);
	for(int i = 1; i <= n; ++i) dn.add(a[i].first, a[i].second, 1, -wi[i]);
	for(int i = 1; i < tot; ++i) dn.add(i, i + 1, inf, 0);
	int ret = -dn.solve(sp, tp).second;
	printf("%d\n", ret);
    return 0;
}
16、星际转移

按时间分层,有两种做法。一种时按时间每次动态加边跑最大流,另一种是直接二分答案建图跑最大流,个人更倾向于后者。二分答案 t i m tim tim 0 0 0 时刻的地球作为 S S S t i m tim tim 时刻的月球作为 T T T,按时间分层建图,对于某个空间站 u u u u t i m − 1 u_{tim-1} utim1 u t i m u_{tim} utim 连容量为 i n f inf inf 的边,表示人可以在某一个空间站停留。对于某一航线相邻的空间站 u u u -> v v v u t i m − 1 u_{tim-1} utim1 v t i m v_{tim} vtim 连容量为太空船容量的边。最后跑最大流判断。

Dinic<int> dn;
vector<int> G[maxn];
int num[maxn];
int n, m, k;

int judge(int tim){

	int sp = 2, tp = tim * n + 1;
	dn.init(tim * n + n);
	for(int i = 1; i <= m; ++i){

		for(int j = 1; j <= tim; ++j){

			int now = G[i][j % sz(G[i])], pre = G[i][(j - 1) % sz(G[i])];
			dn.add((j - 1) * n + pre, j * n + now, num[i]);
		}
	}
	for(int i = 1; i <= n; ++i){

		for(int j = 1; j <= tim; ++j){

			dn.add((j - 1) * n + i, j * n + i, inf);
		}
	}
	return dn.solve(sp, tp) >= k;
}

int main() {

	scanf("%d%d%d", &n, &m, &k);
	n += 2;
	for(int i = 1; i <= m; ++i){

		scanf("%d", &num[i]);
		int x; scanf("%d", &x);
		while(x--){

			int tmp; scanf("%d", &tmp);
			G[i].pb(tmp + 2);
		}
	}
	int l = 1, r = 1000, mid, ret = 0;
	while(l <= r){

		mid = gmid;
		if(judge(mid)) r = mid - 1, ret = mid;
		else l = mid + 1;
	}
	printf("%d\n", ret);
    return 0;
}
17、孤岛营救

黑人问号。状压 b f s bfs bfs

const int xp[] = {0, 0, -1, 1};
const int yp[] = {-1, 1, 0, 0};
struct Node{
	int x, y, sta, cnt;
};
vector<int> G[maxn][maxn];
int mp[10][10][10][10], dis[10][10][1 << 10];
int n, m, p, k, s;

int check(int x, int y){

	return x >= 0 && x < n && y >= 0 && y < m;
}

int bfs(){

	queue<Node> q;
	int lim = 1 << p;
	for(int i = 0; i < n; ++i)
		for(int j = 0; j < m; ++j)
			for(int k = 0; k < lim; ++k) dis[i][j][k] = inf;
	int sta = 0;
	for(auto x : G[0][0]) sta |= 1 << (x - 1);
	dis[0][0][0] = 0, q.push({0, 0, sta, 0});
	while(!q.empty()){

		Node now = q.front(); q.pop();
		if(now.x == n - 1 && now.y == m - 1) return now.cnt;
		for(int i = 0; i < 4; ++i){

			Node nxt = {now.x + xp[i], now.y + yp[i], now.sta, now.cnt + 1};
			if(!check(nxt.x, nxt.y)) continue;
			int g = mp[now.x][now.y][nxt.x][nxt.y];
			if(g == -1 || g > 0 && ((1 << (g - 1)) & now.sta) == 0) continue;
			for(auto x : G[nxt.x][nxt.y]) nxt.sta |= 1 << (x - 1);
			if(dis[nxt.x][nxt.y][nxt.sta] > dis[now.x][now.y][now.sta] + 1){

				dis[nxt.x][nxt.y][nxt.sta] = nxt.cnt;
				q.push(nxt);
			}
		}
	}
	return -1;
}

int main() {

	scanf("%d%d%d", &n, &m, &p);
	scanf("%d", &k);
	while(k--){

		int x1, y1, x2, y2, g; scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &g);
		--x1, --y1, --x2, --y2;
		if(g == 0) mp[x1][y1][x2][y2] = mp[x2][y2][x1][y1] = -1;
		else mp[x1][y1][x2][y2] = mp[x2][y2][x1][y1] = g;
	}
	scanf("%d", &s);
	while(s--){

		int x, y, q; scanf("%d%d%d", &x, &y, &q);
		--x, --y;
		G[x][y].pb(q);
	}
	int ret = bfs();
	printf("%d\n", ret);
    return 0;
}
18、航空路线

即求从 S S S T T T 两条不相交的经过点最多的路径。拆点,除起点终点外容量为 1 1 1,起点终点容量为 2 2 2,对于每个点,费用设置为 − 1 -1 1,再求最小费用最大流。

void print(int n, string s[]){

	for(int u = sp; u != tp; ){

		for(auto e : G[u]){

			if(!G[e.v][e.rev].cap) continue;
			--G[e.v][e.rev].cap;
			u = e.v;
			if(e.cot) cout << s[e.v - n] << endl;
			break;
		}
	}
	for(int u = tp; u != sp; ){

		for(auto e : G[u]){

			if(!e.cap) continue;
			--e.cap;
			u = e.v;
			if(e.cot && e.v != n) cout << s[e.v] << endl;
			break;
		}
	}
}
	
Dinic<int> dn;
map<string, int> mp;
string s[maxn];
int n, m;

int main() {

	scanf("%d%d", &n, &m);
	int sp = n * 2 + 1, tp = sp + 1;
	dn.init(tp);
	for(int i = 1; i <= n; ++i){

		cin >> s[i];
		if(i == 1 || i == n) dn.add(i, n + i, 2, -1);
		else dn.add(i, n + i, 1, -1);
		mp[s[i]] = i;
	}
	dn.add(sp, 1, 2, 0);
	dn.add(n + n, tp, 2, 0);
	while(m--){

		string u, v; cin >> u >> v;
		dn.add(n + mp[u], mp[v], inf, 0);
	}
	int ret = -dn.solve(sp, tp).second;
	if(!ret) { cout << "No Solution!" << endl; return 0; }
	cout << ret - 2 << endl;
	dn.print(n, s);
    return 0;
}
19、汽车加油行驶

汽车只能走 k k k 步,分层图最短路,第 i i i 层图代表汽车还能走 k − i k-i ki 步,按照题意建图跑最短路即可。狂 w a wa wa 一个地方是新设加油站需费用 C C C (不包含加油费 A A A),不是说无需付加油费。

inline int getId(int x, int y, int t){

	return t * n * n + (x - 1) * n + y;
}

int main() {

	scanf("%d%d%d%d%d", &n, &k, &a, &b, &c);
	for(int i = 1; i <= n; ++i){

		for(int j = 1; j <= n; ++j){

			scanf("%d", &mp[i][j]);
		}
	}
	for(int i = 1; i <= n; ++i){

		for(int j = 1; j <= n; ++j){

			for(int o = 1; o <= k; ++o){

				int id = getId(i, j, mp[i][j] ? 0 : o);
				int w = mp[i][j] ? a : 0;
				if(i > 1) G[getId(i - 1, j, o - 1)].pb({w, id});
				if(j > 1) G[getId(i, j - 1, o - 1)].pb({w, id});
				if(i < n) G[getId(i + 1, j, o - 1)].pb({b + w, id});
				if(j < n) G[getId(i, j + 1, o - 1)].pb({b + w, id});
				G[getId(i, j, o)].pb({c + a, getId(i, j, 0)});
			}
		}
	}
	int t = n * n * (k + 1) + 1;
	for(int i = 0; i <= k; ++i) G[getId(n, n, i)].pb({0, t});
	int ret = dij(1, t);
	printf("%d\n", ret);
    return 0;
}
20、深海机器人

直接在网格图上连边,求最小费用最大流即可。

Dinic<int> dn;
int n, m, a, b;

inline int getId(int x, int y){

	return (x - 1) * m + y;
}

int main() {

	scanf("%d%d%d%d", &a, &b, &n, &m);
	++n, ++m;
	int sp = n * m + 1, tp = sp + 1;
	dn.init(tp);
	for(int i = 1; i <= n; ++i){

		for(int j = 1; j < m; ++j){

			int tmp; scanf("%d", &tmp);
			dn.add(getId(i, j), getId(i, j + 1), 1, -tmp);
			dn.add(getId(i, j), getId(i, j + 1), inf, 0);
		}
	}
	for(int j = 1; j <= m; ++j){

		for(int i = 1; i < n; ++i){

			int tmp; scanf("%d", &tmp);
			dn.add(getId(i, j), getId(i + 1, j), 1, -tmp);
			dn.add(getId(i, j), getId(i + 1, j), inf, 0);
		}
	}
	while(a--){

		int k, x, y; scanf("%d%d%d", &k, &x, &y); ++x, ++y;
		dn.add(sp, getId(x, y), k, 0);
	}
	while(b--){

		int r, x, y; scanf("%d%d%d", &r, &x, &y); ++x, ++y;
		dn.add(getId(x, y), tp, r, 0);
	}
	int ret = -dn.solve(sp, tp).second;
	printf("%d\n", ret);
    return 0;
}
21、火星探险

拆点,岩石点内部先连 ( 1 , − 1 ) (1, -1) (1,1) 的边,除障碍外连 ( i n f , 0 ) (inf, 0) (inf,0) 的边,每个点向右、向下连 ( i n f , 0 ) (inf, 0) (inf,0) 的点, S S S 向左上角点连 ( k , 0 ) (k, 0) (k,0) 的边,右下角点向 T T T ( k , 0 ) (k, 0) (k,0) 的边,求最小费用最大流。

void print(int n, int m, int ret){

	int tot = n * m;
	for(int i = 1; i <= ret; ++i){

		int u = tot + 1, aim = tot * 2;
		while(u != aim){

			for(auto e : G[u]){

				if(!G[e.v][e.rev].cap || e.v == u - tot || e.v > u) continue;
				--G[e.v][e.rev].cap;
				printf("%d %d\n", i, (u - tot - 1) / m == (e.v - 1) / m);
				u = e.v + tot;
				break;
			}
		}
	}
}

Dinic<int> dn;
int a[105][105], id[105][105];
int n, m, k;

int main() {

	scanf("%d%d%d", &k, &m, &n);
	int tot = 0;
	for(int i = 1; i <= n; ++i){

		for(int j = 1; j <= m; ++j){

			scanf("%d", &a[i][j]);
			id[i][j] = ++tot;
		}
	}
	int sp = tot * 2 + 1, tp = sp + 1;
	dn.init(tp);
	for(int i = 1; i <= n; ++i){

		for(int j = 1; j <= m; ++j){

			if(a[i][j] == 2) dn.add(id[i][j], tot + id[i][j], 1, -1);
			if(a[i][j] != 1) dn.add(id[i][j], tot + id[i][j], k, 0);
			if(i < n) dn.add(tot + id[i][j], id[i + 1][j], k, 0);
			if(j < m) dn.add(tot + id[i][j], id[i][j + 1], k, 0);
		}
	}
	dn.add(sp, id[1][1], k, 0);
	dn.add(tot + id[n][m], tp, k, 0);
	int ret = dn.solve(sp, tp).first;
	dn.print(n, m, ret);
    return 0;
}
22、骑士共存

将棋盘黑白染色,左侧黑点,右侧白点,冲突点之间连容量为 i n f inf inf 的边, S S S 向所有黑点连容量为 1 1 1 的边,所有白点向 T T T 连容量为 1 1 1 的边,求最小割即可得答案。

const int xp[] = {-2, -2, -1, -1, 1, 1, 2, 2};
const int yp[] = {-1, 1, -2, 2, -2, 2, -1, 1};
Dinic<int> dn;
int a[205][205];
int n, m;

int check(int x, int y){

	return x >= 1 && x <= n && y >= 1 && y <= n;
}

int getId(int x, int y){

	return (x - 1) * n + y;
}

int getCol(int x, int y){

	return (x & 1) && (y & 1) || (~x & 1) && (~y & 1);
}

int main() {

	scanf("%d%d", &n, &m);
	int ret = n * n - m;
	while(m--){

		int x, y; scanf("%d%d", &x, &y);
		a[x][y] = 1;
	}
	int sp = n * n + 1, tp = sp + 1;
	dn.init(tp);
	for(int i = 1; i <= n; ++i){

		for(int j = 1; j <= n; ++j){

			if(a[i][j]) continue;
			int id = getId(i, j), c = getCol(i, j);
			if(c){

				dn.add(id, tp, 1);
				continue;
			}
			dn.add(sp, id, 1);
			for(int k = 0; k < 8; ++k){

				int x = i + xp[k], y = j + yp[k];
				if(!check(x, y) || a[x][y] || getCol(x, y) == c) continue;
				dn.add(id, getId(x, y), inf);
			}
		}
	}
	ret -= dn.solve(sp, tp);
	printf("%d\n", ret);
    return 0;
}
23、最长 k 可重线段集

基本做法同最长 k k k 可重区间集,只需要有 x x x 坐标即可,然后权值再改改,但有特殊情况。当线段垂直于 x x x 轴时,对于区间为 [ x i 1 , x i 1 ] [x_{i1},x_{i1}] [xi1,xi1],而其他线段对应区间 ( x i 1 , x i 2 ) (x_{i1},x_{i2}) (xi1,xi2),区间有开有闭,难以处理,并且当 x i 1 x_{i1} xi1 x i 2 x_{i2} xi2 相等时直接连边会有自环。将每个点拆分成两个,分别代表端点闭合或者不闭合,每个区间都表示为左闭右开,具体为 [ x , x ] [x,x] [x,x] -> [ 2 x , 2 x + 1 ] [2x,2x+1] [2x,2x+1] ( x 1 , x 2 ) (x_1,x_2) (x1,x2) -> [ 2 x 1 + 1 , 2 x 2 ) [2x_1+1,2x_2) [2x1+1,2x2)

Dinic<int> dn;
pii a[maxn];
int xi[maxn], wi[maxn];
int n, k, tot;

#define pw(x) ((x)*(x))

int main() {

	scanf("%d%d", &n, &k);
	for(int i = 1; i <= n; ++i){

		int x1, y1, x2, y2; scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
		wi[i] = sqrt(pw((ll)x1 - x2) + pw((ll)y1 - y2));
		x1 <<= 1, x2 <<= 1;
		if(x1 == x2) ++x2;
		else ++x1;
		a[i].first = x1, a[i].second = x2;
		xi[++tot] = x1, xi[++tot] = x2;
	}
	sort(xi + 1, xi + 1 + tot);
	tot = unique(xi + 1, xi + 1 + tot) - xi - 1;
	for(int i = 1; i <= n; ++i){

		a[i].first = lower_bound(xi + 1, xi + 1 + tot, a[i].first) - xi;
		a[i].second = lower_bound(xi + 1, xi + 1 + tot, a[i].second) - xi;
	}
	int sp = tot + 1, tp = sp + 1;
	dn.init(tp);
	dn.add(sp, 1, k, 0), dn.add(tot, tp, k, 0);
	for(int i = 1; i <= n; ++i) dn.add(a[i].first, a[i].second, 1, -wi[i]);
	for(int i = 1; i < tot; ++i) dn.add(i, i + 1, inf, 0);
	int ret = -dn.solve(sp, tp).second;
	printf("%d\n", ret);
    return 0;
}

 
T i p s Tips Tips:以上题目均仅在 L i b r e O J LibreOJ LibreOJ 上提交通过。

(话说怎么只有 23 23 23 道题)

链接:https://loj.ac/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值