[二分图&最小割]

OTL@assassain

反转源汇的模型: 给定一个二分图,同时选择集合中的两个点会有一个代价,选择每一个点有一个收益,问最大收益是多少

(即两个点在不同的集合中是有冲突关系的)

解法: 用最小割模型解决,通过反转源汇来表示冲突关系,用源S汇T表示选或不选,左边的黑点向S连黑点选择的收益(如果这条边割掉了就代表没有选择这个黑点,要减掉这个代价),向T连黑点不选择的收益(可以没有)。右边的白点向S连白点不选择的收益,向T连白点选择的收益(此时把S,T和上述反转了一下)。那么原图中两个点共同选择的代价就是在网络流图中直接连原来的权值即可

其实模型和另外一个模型很有对比意义,即最大权闭合子图

模型是给定一个二分图,选择其中一个点会有另一个点必须被选择(这时可以用inf的边来表示冲突关系),选择的收益有正有负(也可以看做有加有减),问最大收益。

那么此时两个点在相同的集合中是没有矛盾的,在不同的集合中是有代价的,此时不需要反转源汇,直接建图跑最小割就可以了

最大利益 = 所有点正权值之和 - 最小割

/*--------------------------------华丽丽的分割线--------------------------------*/

[国家集训队2011]圈地计划 网格图黑白染色,注意连双向边

#define MAXN 110
#include <bits/stdc++.h>
 
using namespace std;
const int inf = 0x7fffffff / 2;
const int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
 
int n, m, S, T, a[MAXN][MAXN], b[MAXN][MAXN], c[MAXN][MAXN];
int h[MAXN*MAXN], cnt = 1;
struct Edge { int to, nxt, w; } edge[2000010];
void addedge(int u, int v, int w) {
	edge[++ cnt] = (Edge){v, h[u], w}; h[u] = cnt;
	edge[++ cnt] = (Edge){u, h[v], 0}; h[v] = cnt;
}
 
int que[MAXN*MAXN], d[MAXN*MAXN];
bool BFS() {
	int head = 0, tail = 0;
	for(int i = S ; i <= T ; ++ i) d[i] = -1;
	que[tail ++] = S, d[S] = 0;
	while(head != tail) {
		int u = que[head ++];
		for(int i = h[u] ; i ; i = edge[i].nxt) {
			if(edge[i].w == 0) continue;
			int v = edge[i].to;
			if(d[v] == -1) d[v] = d[u] + 1, que[tail ++] = v;
		}
	} return d[T] != -1;
}
 
int DFS(int x, int a) {
	if(x == T || a == 0) return a;
	int used = 0, f;
	for(int i = h[x] ; i ; i = edge[i].nxt) {
		int v = edge[i].to;
		if(d[v] == d[x] + 1) {
			f = DFS(v, min(a-used, edge[i].w));
			edge[i].w -= f;
			edge[i^1].w += f;
			used += f;
			if(used == a) return used;
		}
	}
	if(!used)d[x] = -1;
	return used;
}
 
int Dinic() {
	int ret = 0;
	while(BFS())
		ret += DFS(S, inf);
	return ret;
}
 
int main() {
	freopen("nt2011_land.in", "r", stdin);
	freopen("nt2011_land.out", "w", stdout);
	scanf("%d%d", &n, &m);
	for(int i = 1 ; i <= n ; ++ i)
		for(int j = 1 ; j <= m ; ++ j)
			scanf("%d", &a[i][j]);
	for(int i = 1 ; i <= n ; ++ i)
		for(int j = 1 ; j <= m ; ++ j)
			scanf("%d", &b[i][j]);
	for(int i = 1 ; i <= n ; ++ i)
		for(int j = 1 ; j <= m ; ++ j)
			scanf("%d", &c[i][j]);
	static int id[MAXN][MAXN], idfclock = 0;
	for(int i = 1 ; i <= n ; ++ i)
		for(int j = 1 ; j <= m ; ++ j)
			id[i][j] = ++ idfclock;
	S = 0, T = ++ idfclock;
	for(int i = 1 ; i <= n ; ++ i)
		for(int j = 1 ; j <= m ; ++ j) {
			if(i + j & 1) {
				addedge(S, id[i][j], a[i][j]);
				addedge(id[i][j], T, b[i][j]);
			}
			else {
				addedge(S, id[i][j], b[i][j]);
				addedge(id[i][j], T, a[i][j]);
			}
			for(int k = 0 ; k < 4 ; ++ k) {
				int x = i + dx[k], y = j + dy[k];
				if(id[x][y])addedge(id[i][j], id[x][y], c[i][j] + c[x][y]);				
			}
		}
	int ans = 0;
	for(int i = 1 ; i <= n ; ++ i)
		for(int j = 1 ; j <= m ; ++ j) {
			int cn = 0;
			for(int k = 0 ; k < 4 ; ++ k) {
				int x = i + dx[k], y = j + dy[k];
				if(id[x][y]) cn ++;
			}
			ans += a[i][j] + b[i][j] + c[i][j] * cn;
		}
 
	printf("%d
", ans - Dinic());
	return 0;
}

[国家集训队2011]男生女生

求一个最大的二分图的子图,使得左边的每一个点向右边的每一个点都有边相连(第一问)

转化成最小割模型后,表示的冲突关系是左边选择一个点,右边和它没有边相连的点就不能选择。

选择一个点的收益为1,不选择的收益为0

那么这道题的建图就是从S->左边的点,右边的点->T连边,中间因为永远不能割断所以边权为inf

还有一些特殊的地方需要处理,但是和反转源汇建图没有太大关系,因此不再赘述

#define MAXN 105
#include <bits/stdc++.h>

using namespace std;
const int inf = 0x7fffffff, md = 19921228;

int n, m, k;
bool a[MAXN][MAXN];

int h[MAXN], cnt = 1, S, T;
struct Edge { int to, nxt, w; } edge[1000010];
void addedge(int u, int v, int w) {
	edge[++ cnt] = (Edge){v, h[u], w}; h[u] = cnt;
	edge[++ cnt] = (Edge){u, h[v], 0}; h[v] = cnt;
}

int d[MAXN], que[MAXN];

bool BFS() {
	int head = 0, tail = 0;
	memset(d, -1, sizeof d);
	que[tail ++] = S, d[S] = 0;
	while(head != tail) {
		int u = que[head ++];
		for(int i = h[u] ; i ; i = edge[i].nxt) {
			if(!edge[i].w) continue;
			int v = edge[i].to;
			if(d[v] == -1) que[tail ++] = v, d[v] = d[u] + 1;	
		}
	} return d[T] != -1;
}

int DFS(int x, int a) {
	if(x == T || a == 0) return a;
	int used = 0, f;
	for(int i = h[x] ; i ; i = edge[i].nxt) {
		int v = edge[i].to;
		if(d[v] == d[x] + 1) {
			f = DFS(v, min(a-used, edge[i].w));
			edge[i].w -= f;
			edge[i^1].w += f;
			used += f;
			if(used == a) return used;
		}
	}
	if(!used)d[x] = -1;
	return used;
}

int Dinic() {
	int ret = 0;
	while(BFS())
		ret += DFS(S, inf);
	return ret;
}

int main() {
	freopen("boygirl.in", "r", stdin);
	freopen("boygirl.out", "w", stdout);
	scanf("%d%d%d", &n, &k, &m);
	int u, v;
	for(int i = 1 ; i <= m ; ++ i) {
		scanf("%d%d", &u, &v);
		a[u][v] = true;
	}
	
	T = n+n+1;
	for(int i = 1 ; i <= n ; ++ i) {
		addedge(S, i, 100), addedge(i+n, T, 99);
		for(int j = 1 ; j <= n ; ++ j)
			if(!a[i][j])addedge(i, j+n, inf);
	}
	
	int fw = Dinic();
	int a = fw - fw/99*99, b = fw/99 - a;	
	a = n-a, b = n-b;
	
	static int C[2510][2510];
	static long long f[MAXN][MAXN];
	memset(C, 0, sizeof C);
	memset(f, 0, sizeof f);
	
	for(int i = 0 ; i <= a*b ; ++ i) {
		C[i][0] = 1;
		for(int j = 1 ; j <= i ; ++ j)
			C[i][j] = (C[i-1][j] + C[i-1][j-1]) % md;
	}
	
	
	for(int i = 1 ; i <= a ; ++ i) {
		for(int j = 1 ; j <= b ; ++ j) {
			f[i][j] = C[i*j][k];
			for(int k = 1 ; k <= i ; ++ k)
				for(int l = 1 ; l <= j ; ++ l)
					if(i != k || l != j) 
						f[i][j] = (f[i][j] - f[k][l]*C[i][k]%md*C[j][l]%md + md) % md;
		}
	}
	
	printf("%d %d
%lld
", a, b, f[a][b]);
	return 0;
}

  

给时光以生命,而不是给生命以时光。
原文地址:https://www.cnblogs.com/Candyouth/p/5655503.html