网络流二元关系 学习笔记

最小割基本模型

\(\text{Part 1}\)

\(\text{Problem}\)

\(n\) 个物品和两个集合 \(A,B\),每个物品属于其中一个集合
将一个物品放到 \(A\) 代价为 \(a_i\),放到 \(B\)\(b_i\)
还有一些限制 \(u,v,w\) 表示 \(u,v\) 不在同一个集合代价为 \(w\)
求最小代价

\(\text{Solution}\)

很容易想到 \(S\) 向每个物品连容量 \(a_i\) 的边
每个物品向 \(T\) 连容量 \(b_i\) 的边
\(u\)\(v\) 连容量为 \(w\) 的双向边
最小割等于最大流即可
为什么正确呢?考虑两个物品 \(u,v\) 一个放入 \(A\) 一个放入 \(B\)
为了使 \(S,T\) 断开,\(u,v\) 之间边必然要割掉,就满足 \(u,v\) 不在同个集合代价为 \(w\)

\(\text{JZOJ 1144. 【GDKOI2010】圈地计划}\)

#include <cstdio>
#include <iostream>
#define RE register
#define IN inline
using namespace std;

const int N = 105, M = N * N * 2, INF = 1e9;
int n, m, S, T, tot = 1, h[M], dep[M], cur[M], Q[M], a[N][N], b[N][N], c[N][N];
int fx[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};

IN int getid(int x, int y){return (x - 1) * m + y;}
struct edge{int to, nxt, w;}e[M * 10];
IN void add(int x, int y, int w)
{
	e[++tot] = edge{y, h[x], w}, h[x] = tot;
	e[++tot] = edge{x, h[y], 0}, h[y] = tot;
}

IN int bfs()
{
	for(RE int i = S; i <= T; i++) cur[i] = h[i], dep[i] = 0;
	int head = 0, tail = 1; Q[1] = S, dep[S] = 1;
	while (head < tail)
	{
		int now = Q[++head];
		for(RE int i = h[now]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if (dep[v] || !e[i].w) continue;
			dep[v] = dep[now] + 1, Q[++tail] = v;
		}
	}
	return dep[T];
}
int dfs(int x, int lim)
{
	if (x == T || lim <= 0) return lim;
	int flow = 0;
	for(RE int i = cur[x], v, f; i; i = e[i].nxt)
	{
		cur[x] = i, v = e[i].to;
		if (dep[v] != dep[x] + 1 || !e[i].w) continue;
		f = dfs(v, min(lim, e[i].w));
		if (f <= 0) continue;
		e[i].w -= f, e[i ^ 1].w += f, lim -= f, flow += f;
		if (lim <= 0) break;
	}
	return flow;
}
IN int dinic(){int flow = 0; while (bfs()) flow += dfs(S, INF); return flow;}

int main()
{
	scanf("%d%d", &n, &m), T = n * m + 1; int sum = 0;
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1; j <= m; j++)
			scanf("%d", &a[i][j]), sum += a[i][j], add(S, getid(i, j), a[i][j]);
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1; j <= m; j++)
			scanf("%d", &b[i][j]), sum += b[i][j], add(getid(i, j), T, b[i][j]);
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1; j <= m; j++) scanf("%d", &c[i][j]);
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1; j <= m; j++)
			for(RE int k = 0; k < 4; k++)
			{
				int x = i + fx[k][0], y = j + fx[k][1];
				if (x > 0 && x <= n && y > 0 && y <= m)
					sum += c[i][j], add(getid(i, j), getid(x, y), c[i][j] + c[x][y]);
			}
	printf("%d\n", sum - dinic());
}

\(\text{Part 2}\)

\(\text{Problem}\)

\(\text{Part 1 Problem}\)
但是 \(u,v,w\) 限制为 \(u,v\) 在同一个集合的代价
其实在和不在是差不多的,考虑将 \(u,v\) 其中一个的 \(a,b\) 代价换过来
\(\text{Part 1 Solution}\) 一样建图
注意到 \(u,v\) 中一个换了一个没换,为区分开来对其进行黑白染色

\(\text{LG P1935 [国家集训队]圈地计划}\)

一个点与上下左右 \(4\) 个点异色,正常黑白染色即可

#include <cstdio>
#include <iostream>
#define RE register
#define IN inline
using namespace std;

const int N = 105, M = N * N, INF = 1e9;
int n, m, S, T, tot = 1, h[M], dep[M], cur[M], Q[M], a[N][N], b[N][N], c[N][N];
int fx[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};

IN int getid(int x, int y){return (x - 1) * m + y;}
struct edge{int to, nxt, w;}e[M * 100];
IN void add(int x, int y, int w)
{
	e[++tot] = edge{y, h[x], w}, h[x] = tot;
	e[++tot] = edge{x, h[y], 0}, h[y] = tot;
}

IN int bfs()
{
	for(RE int i = S; i <= T; i++) cur[i] = h[i], dep[i] = 0;
	int head = 0, tail = 1; Q[1] = S, dep[S] = 1;
	while (head < tail)
	{
		int now = Q[++head];
		for(RE int i = h[now]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if (dep[v] || !e[i].w) continue;
			dep[v] = dep[now] + 1, Q[++tail] = v;
		}
	}
	return dep[T];
}
int dfs(int x, int lim)
{
	if (x == T || lim <= 0) return lim;
	int flow = 0;
	for(RE int i = cur[x], v, f; i; i = e[i].nxt)
	{
		cur[x] = i, v = e[i].to;
		if (dep[v] != dep[x] + 1 || !e[i].w) continue;
		f = dfs(v, min(lim, e[i].w));
		if (f <= 0) continue;
		e[i].w -= f, e[i ^ 1].w += f, lim -= f, flow += f;
		if (lim <= 0) break;
	}
	return flow;
}
IN int dinic(){int flow = 0; while (bfs()) flow += dfs(S, INF); return flow;}

int main()
{
	scanf("%d%d", &n, &m), T = n * m + 1; int sum = 0;
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1; j <= m; j++) scanf("%d", &a[i][j]), sum += a[i][j];
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1; j <= m; j++) scanf("%d", &b[i][j]), sum += b[i][j];
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1; j <= m; j++)
			if ((i + j) & 1) add(S, getid(i, j), a[i][j]), add(getid(i, j), T, b[i][j]);
			else add(S, getid(i, j), b[i][j]), add(getid(i, j), T, a[i][j]);
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1; j <= m; j++) scanf("%d", &c[i][j]);
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1; j <= m; j++)
			for(RE int k = 0; k < 4; k++)
			{
				int x = i + fx[k][0], y = j + fx[k][1];
				if (x > 0 && x <= n && y > 0 && y <= m)
					sum += c[i][j], add(getid(i, j), getid(x, y), c[i][j] + c[x][y]);
			}
	printf("%d\n", sum - dinic());
}

\(\text{Part 3}\)

补充的一个比较强大的建图
如果 \(u,v\) 同时选入 \(A\) 产生代价,同时选入 \(B\) 产生代价另一代价
不需要黑白染色,考虑这样的建图
新建点 \(x\)\(S\) 连向 \(x\) 边权为代价,\(x\) 连向 \(u,v\) 边权为 \(INF\)
同理连 \(T\) 处理
例如:

国家集训队 \(\text{happiness}\)

\(\text{Code}\)

#include <cstdio>
#include <iostream>
#define RE register
#define IN inline
using namespace std;

const int N = 101, M = N * N * 5, INF = 1e9;
int n, m, S, T, tot = 1, h[M], dep[M], cur[M], Q[M];
int fx[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};

IN int getid(int x, int y){return (x - 1) * m + y;}
struct edge{int to, nxt, w;}e[M * 10];
IN void add(int x, int y, int w)
{
	e[++tot] = edge{y, h[x], w}, h[x] = tot;
	e[++tot] = edge{x, h[y], 0}, h[y] = tot;
}

IN int bfs()
{
	for(RE int i = S; i <= T; i++) cur[i] = h[i], dep[i] = 0;
	int head = 0, tail = 1; Q[1] = S, dep[S] = 1;
	while (head < tail)
	{
		int now = Q[++head];
		for(RE int i = h[now]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if (dep[v] || !e[i].w) continue;
			dep[v] = dep[now] + 1, Q[++tail] = v;
		}
	}
	return dep[T];
}
int dfs(int x, int lim)
{
	if (x == T || lim <= 0) return lim;
	int flow = 0;
	for(RE int i = cur[x], v, f; i; i = e[i].nxt)
	{
		cur[x] = i, v = e[i].to;
		if (dep[v] != dep[x] + 1 || !e[i].w) continue;
		f = dfs(v, min(lim, e[i].w));
		if (f <= 0) continue;
		e[i].w -= f, e[i ^ 1].w += f, lim -= f, flow += f;
		if (lim <= 0) break;
	}
	return flow;
}
IN int dinic(){int flow = 0; while (bfs()) flow += dfs(S, INF); return flow;}

int main()
{
	scanf("%d%d", &n, &m), T = n * m * 5 + 1; int sum = 0, sz = n * m;
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1, x; j <= m; j++)
			scanf("%d", &x), sum += x, add(S, getid(i, j), x);
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1, x; j <= m; j++)
			scanf("%d", &x), sum += x, add(getid(i, j), T, x);
	for(RE int i = 1; i < n; i++)
		for(RE int j = 1, x, z; j <= m; j++)
		{
			scanf("%d", &x), sum += x, z = getid(i, j) + sz;
			add(S, z, x), add(z, getid(i, j), INF), add(z, getid(i + 1, j), INF);
		}
	for(RE int i = 1; i < n; i++)
		for(RE int j = 1, x, z; j <= m; j++)
		{
			scanf("%d", &x), sum += x, z = getid(i, j) + sz * 2;
			add(z, T, x), add(getid(i, j), z, INF), add(getid(i + 1, j), z, INF);
		}
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1, x, z; j < m; j++)
		{
			scanf("%d", &x), sum += x, z = getid(i, j) + sz * 3;
			add(S, z, x), add(z, getid(i, j), INF), add(z, getid(i, j + 1), INF);
		}
	for(RE int i = 1; i <= n; i++)
		for(RE int j = 1, x, z; j < m; j++)
		{
			scanf("%d", &x), sum += x, z = getid(i, j) + sz * 4;
			add(z, T, x), add(getid(i, j), z, INF), add(getid(i, j + 1), z, INF);
		}
	printf("%d\n", sum - dinic());
}
posted @ 2022-01-22 19:48  leiyuanze  阅读(76)  评论(0编辑  收藏  举报