最小割练习

网络战争

与01分数规划相结合
(dfrac{sum W_e}{|C|} < lambda) 对应 (sum(W_e - lambda) < 0)
二分 (lambda) 找到 (dfrac{sum W_e}{|C|}) 的最小值
(W_e - lambda le 0) 的边必选, 剩余的集合内的边一定不选,则边割集与流网络的割相对应, 求一个最小割即可

#include <bits/stdc++.h>
using namespace std;

const int N = 100 + 10;
const int M = (400 + 10) * 2;
const int INF = 1e9;
const double eps = 1e-8;

int n, m, S, T;
struct Edge
{
	int to, nxt, w;
	double flow;
}line[M];
int fist[N], idx;
int cur[N], d[N];

void add(int x, int y, int z)
{
	line[idx] = {y, fist[x], z, 0};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], z, 0};
	fist[y] = idx ++; 
}

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow > 0)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v);
			}
		} 
	} 
	return 0;
}

double find(int u, double limit)
{
	if(u == T) return limit;
	double flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow > 0)
		{
			double t = find(v, min(line[i].flow, limit - flow));
			if(t <= 0) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		} 
	}
	return flow;
}

double dinic(double mid)
{
	double res = 0;
	for(int i = 0; i < idx; i += 2)
		if(line[i].w <= mid) 
		{
			res += line[i].w - mid;
			line[i].flow = line[i ^ 1].flow = 0; 
		}
		else line[i].flow = line[i ^ 1].flow = line[i].w - mid;
	double r = 0, flow;
	while(bfs()) while(flow = find(S, INF)) r += flow;
	return res + r;
}
 
int main()
{
	scanf("%d%d%d%d", &n, &m, &S, &T);
	memset(fist, -1, sizeof fist);
	for(int i = 1; i <= m; ++ i)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c);
	}
	
	double l = 0, r = 1e7;
	while(r - l > eps)
	{
		double mid = (l + r) / 2;
		if(dinic(mid) < 0) r = mid;
		else l = mid;
	}
	
	printf("%.2lf
", r);
	return 0;
} 

最优标号

每一位之间互不影响, 拆成每一位处理
把集合分为两类,一类是当前位为 (0) 的点, 一类是当前位为 (1) 的点
集合内部的点异或值为 (0) ,这些边不会对答案有贡献, 集合间的边异或值为 (1), 对答案有贡献
为了最小化边的数量, 跑最小割即可
建立虚拟源点和汇点

  • 从源点向当前位初始值为 (0) 的点连一条容量为 (+infty) 的边.(保证该点一定和 (s) 在一个集合)
  • 从当前位初始值为 (1) 的点向汇点连一条容量为 (+infty) 的边.(保证该点一定和 (t) 在一个集合)
#include <bits/stdc++.h>
using namespace std;

#define x first
#define y second
typedef pair<int, int> PII; 
typedef long long LL;
const int N = 500 + 10;
const int M = (3000 + N) * 2;
const int INF = 1e9;

int n, m, k, S, T;
struct Edge
{
	int to, nxt, flow;
}line[M]; 
int fist[N], idx;
int cur[N], d[N];
int p[N];
PII edges[3010];

void add(int x, int y, int z)
{
	line[idx] = {y, fist[x], z};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], z};
	fist[y] = idx ++;
}

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v); 
			}
		} 
	}
	return 0;
}

int find(int u, int limit)
{
	if(u == T) return limit;
	int flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow)
		{
			int t = find(v, min(line[i].flow, limit - flow));
			if(!t) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		}
	}
	return flow;
}

LL dinic(int k)
{
	memset(fist, -1, sizeof fist);
	idx = 0;
	for(int i = 1; i <= m; ++ i)
	{
		int a = edges[i].x, b = edges[i].y;
		add(a, b, 1);
	}
	for(int i = 1; i <= n; ++ i)
		if(p[i] >= 0)
		{
			if(p[i] >> k & 1) add(i, T, INF);
			else add(S, i, INF); 
		}
	int res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res;
}

int main()
{
	scanf("%d%d", &n, &m);
	S = 0, T = n + 1;
	memset(fist, -1, sizeof fist);
	for(int i = 1; i <= m; ++ i) scanf("%d%d", &edges[i].x, &edges[i].y);
	scanf("%d", &k);
	memset(p, -1, sizeof p);
	for(int i = 1; i <= n; ++ i)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		p[a] = b;
	}
	LL res = 0;
	for(int i = 0; i <= 30; ++ i) res += dinic(i) << i;
	printf("%lld
", res); 
	return 0;
} 

最大获利

最大权闭合图做法
中转站的权值设为负值, 用户群的权值设为正值, 从用户群向可以提供服务的中转站连边, 该问题转化为最大权闭合图

  • 从源点向权值为正的点连一条容量为权值的边
  • 从权值为负的点向汇点连一条容量为权值的绝对值的边
  • 原图内部的边的容量设为 (+infty)
#include <bits/stdc++.h>
using namespace std;

const int N = 50000 + 5000 + 10;
const int M = (N + 50000 * 2) * 2;
const int INF = 1e9;

int n, m, S, T;
struct Edge
{
	int to, nxt, flow;
}line[M];
int fist[N], idx;
int cur[N], d[N];

void add(int x, int y, int z)
{
	line[idx] = {y, fist[x], z};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], 0};
	fist[y] = idx ++;	
}

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v);
			}
		} 
	}
	return 0;
}

int find(int u, int limit)
{
	if(u == T) return limit;
	int flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)	
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow)
		{
			int t = find(v, min(line[i].flow, limit - flow));
			if(!t) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		}
	}
	return flow;
}

int dinic()
{
	int res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res;
}

int main()
{
	scanf("%d%d", &n, &m);
	S = 0, T = n + m + 1;
	memset(fist, -1, sizeof fist);
	for(int i = 1; i <= n; ++ i) 
	{
		int x;
		scanf("%d", &x);
		add(m + i, T, x); 
	} 
	int res = 0;
	for(int i = 1; i <= m; ++ i)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		res += c;
		add(S, i, c);
		add(i, a + m, INF);
		add(i, b + m, INF);
	}
	printf("%d
", res - dinic());
	return 0;
}

最大密度子图做法
把用户群直接看做是一条边,转化成无向图,获益转化为边权, 建造费用的负值转化为点权
目的是使得 (|V''| + |E'|) 最大, 与点和边带权的最大密度子图最大化的值 (|E'| + |V''| - g * |V'|) 对应, 即在 $ g = 0$ 时最大
建图:

  • 从源点向每个基站连一条容量为 (U) 的边
  • 从每个基站向汇点连一条容量为 (2 * g - dv - 2 * pv + U) 的边
  • 两个基站之间的双向边的容量设为边权

(c(S, T) = U * n + 2 * g * |V'| - 2 * |E'| - 2 * |V''| = U * n - 2 * |E'| - 2 * |V''|)
找到了最小割和 (|V''| + |E'|) 之间的关系, 求最小割即可使目标值最大化

#include <bits/stdc++.h>
using namespace std;

const int N = 5000 + 10;
const int M = (N * 2 + 50000 + 10) * 2;
const int INF = 1e9;

int n, m, S, T;
struct Edge
{
	int to, nxt, flow;
}line[M];
int fist[N], idx;
int cur[N], d[N];
int dg[N], p[N];

void add(int x, int y, int z1, int z2)
{
	line[idx] = {y, fist[x], z1};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], z2};
	fist[y] = idx ++;
}

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v);
			}
		}
	}
	return 0;
}

int find(int u, int limit)
{
	if(u == T) return limit;
	int flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)	
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow)
		{
			int t = find(v, min(line[i].flow, limit - flow));
			if(!t) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		}
	}
	return flow;
}

int dinic()
{
	int res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res;
} 

int main()
{
	scanf("%d%d", &n, &m);
	S = 0, T = n + 1;
	memset(fist, -1, sizeof fist);
	for(int i = 1; i <= n; ++ i) 
	{
		scanf("%d", &p[i]); 
		p[i] *= -1;
	}
	for(int i = 1; i <= m; ++ i)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c, c);
		dg[a] += c, dg[b] += c;
	}
	int U = 0;
	for(int i = 1; i <= n; ++ i) U = max(U, 2 * p[i] + dg[i]);
	for(int i = 1; i <= n; ++ i)
	{
		add(S, i, U, 0);
		add(i, T, U - 2 * p[i] - dg[i], 0); 
	}
	
	printf("%d
", (U * n - dinic()) / 2);
	return 0;
}

生活的艰辛

最大密度子图
最大化 (dfrac{|E'|}{|V'|}),转化成判断最大化 (|E'| - g * |V'|)(0) 的关系
建图:

  • 从源点向所有点连一条容量为 (U) 的边
  • 从所有点向汇点连一条容量为 (2 * g - dv + U) 的边
  • 图中原有边的容量设为 (1)

(c(S, T) = U * n + 2 * g * |V'| - 2 * |E'|)
找到了割与 (|E'| - g * |V'|) 的关系, 求最小割即可

#include <bits/stdc++.h>
using namespace std;

#define x first
#define y second
typedef pair<int, int> PII;

const int N = 100 + 10;
const int M = (N * 2 + 1000 + 10) * 2;
const int INF = 1e9;
const double eps = 1e-8;

int n, m, S, T, U;
struct Edge
{
	int to, nxt;
	double flow;
}line[M];
int fist[N], idx;
int cur[N], d[N];
int dg[N];
PII p[M];

int ans;
bool st[N];

void add(int x, int y, double z1, double z2)
{
	line[idx] = {y, fist[x], z1};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], z2};
	fist[y] = idx ++;
}

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow > 0)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v);
			}
		} 
	}
	return 0;
}

double find(int u, double limit)
{
	if(u == T) return limit;
	double flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow > 0)
		{
			double t = find(v, min(line[i].flow, limit - flow));
			if(t <= 0) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t; 
		}
	}
	return flow;
}

double dinic(double g)
{
	memset(fist, -1, sizeof fist);
	idx = 0;
	for(int i = 1; i <= m; ++ i)
		add(p[i].x, p[i].y, 1, 1);
	for(int i = 1; i <= n; ++ i)
	{
		add(S, i, U, 0);
		add(i, T, U + g * 2 - dg[i], 0);
	}
	double res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res; 
}

void dfs(int u)
{
	st[u] = 1;
	if(u != S) ans ++;
	for(int i = fist[u]; i != -1; i = line[i].nxt)
	{
		int v = line[i].to;
		if(!st[v] && line[i].flow > 0) dfs(v);
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	S = 0, T = n + 1;
	
	for(int i = 1; i <= m; ++ i)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		dg[a] ++, dg[b] ++;
		p[i] = {a, b};
	}
	
	U = m;
	
	double l = 0, r = m;
	while(r - l > eps)
	{
		double mid = (r + l) / 2;
		double t = dinic(mid);
		if(U * n - t > 0) l = mid;
		else r = mid;
	}
	
	dinic(l);
	dfs(S);
	
	if(!ans) puts("1
1");
	else 
	{
		printf("%d
", ans);
		for(int i = 1; i <= n; ++ i)
			if(st[i])
				printf("%d
", i);
	} 
	return 0; 
}

有向图破坏

最小点权覆盖集
如果有一条从 (a)(b) 的边, 那么 (W_a^-)(W_b^+) 必须选一个来删掉这条边
那么考虑把每个点拆点构造一个二分图, 第一排点都是作为出点的点, 第二排点都是作为入点的点
若有一条从 (a)(b) 的边, 则从 (a^-)(b^+) 连一条边无向边
则该问题转化为一个最小点权覆盖问题

  • 从源点向入点连一条容量为 (W_i^+) 的边
  • 从出点向汇点连一条容量为 (W_i^-) 的边
  • 若有一条从 (a)(b) 的边, 则从 (b^+)(a^-) 连一条容量为 (+infty) 的边

最终找到割边输出方案

#include <bits/stdc++.h>
using namespace std;

const int N = 100 * 2 + 10;
const int M = (5000 + N + 10) * 2;
const int INF = 1e9;
 
int n, m, S, T;
struct Edge
{
	int to, nxt, flow;
}line[M];
int fist[N], idx;
int cur[N], d[N];
bool st[N];

void add(int x, int y, int z)
{
	line[idx] = {y, fist[x], z};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], 0};
	fist[y] = idx ++; 
} 

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v);
			}
		} 
	} 
	return 0;
}

int find(int u, int limit)
{
	if(u == T) return limit;
	int flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow)
		{
			int t = find(v, min(line[i].flow, limit - flow));
			if(!t) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		}
	}
	return flow;
}

int dinic()
{
	int res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res; 
} 

void dfs(int u)
{
	st[u] = 1;
	for(int i = fist[u]; i != -1; i = line[i].nxt)
	{
		int v = line[i].to;
		if(!st[v] && line[i].flow) dfs(v);
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	S = 0, T = 2 * n + 1;
	memset(fist, -1, sizeof fist);
	for(int i = 1; i <= n; ++ i)
	{
		int w;
		scanf("%d", &w);
		add(S, i, w);
	} 
	for(int i = 1; i <= n; ++ i)
	{
		int w;
		scanf("%d", &w);
		add(n + i, T, w);
	}
	for(int i = 1; i <= m; ++ i)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(b, a + n, INF);
	}
	
	printf("%d
", dinic());
	
	dfs(S);
	
	int res = 0;
	for(int i = 0; i < idx; i += 2)
	{
		int u = line[i ^ 1].to, v = line[i].to;
		if(st[u] && !st[v]) res ++;
	}
	
	printf("%d
", res);
	for(int i = 0; i < idx; i += 2)
	{
		int u = line[i ^ 1].to, v = line[i].to;
		if(st[u] && !st[v])  
		{
			if(u == S) printf("%d +
", v);
			if(v == T) printf("%d -
", u - n);
		} 
	}
	return 0;
} 

王者之剑

最大点权独立集

  • 只能在偶数秒拿宝石
  • 不可能同时拿走相邻格子上的宝石,把格子看成点,相邻格子之间连一条边,将格子黑白染色,可以形成一个二分图,则我们选择的格子一定是二分图上的独立集
  • 两行两行走,对于任何一个独立集,可以构造出一种合法方案

故所有合法方案等价于二分图的一个独立集, 求一个最大点权独立集即可

#include <bits/stdc++.h>
using namespace std;

const int N = 10000 + 10;
const int M = (N + 5000 * 4 + 10) * 2;
const int INF = 1e9; 
const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, -1, 1};

int n, m, S, T;
struct Edge
{
	int to, nxt, flow;
}line[M];
int fist[N], idx;
int cur[N], d[N];

int get(int x, int y)
{
	return (x - 1) * m + y;	
}

void add(int x, int y, int z)
{
	line[idx] = {y, fist[x], z};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], 0};
	fist[y] = idx ++;	
}

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v);
			}
		}
	}
	return 0;
}

int find(int u, int limit)
{
	if(u == T) return limit;
	int flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow)
		{
			int t = find(v, min(line[i].flow, limit - flow));
			if(!t) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		}
	}
	return flow;
}

int dinic()
{
	int res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res;	
}

int main()
{
	scanf("%d%d", &n, &m);
	S = 0, T = n * m + 1;
	memset(fist, -1, sizeof fist);
	
	int tot = 0;
	for(int i = 1; i <= n; ++ i)
		for(int j = 1; j <= m; ++ j)
		{
			int w;
			scanf("%d", &w);
			if(i + j & 1) 
			{
				add(S, get(i, j), w);
				for(int k = 0; k < 4; ++ k)
				{
					int x = i + dx[k], y = j + dy[k];
					if(x >= 1 && x <= n && y >= 1 && y <= m)
						add(get(i, j), get(x, y), INF);
				}
			}
			else add(get(i, j), T, w);
			tot += w;
		}	
	printf("%d
", tot - dinic());
	return 0;
}

有线电视网络

使得两个点不连通, 枚举这两个点作为源点和汇点
要删去的是点,为了和割边产生联系,我们考虑拆点,删去某个点等价于把该点的入点到出点之间的边选为割边, 把不能被选为割边的边容量设为 (+infty)

  • 每个点的入点到出点连一条容量为 (1) 的边
  • 内部边不能被选为割边,它的容量设为 (+infty)

最小割即为使源点和汇点不连通时,需要删去的最少点数

#include <bits/stdc++.h>
using namespace std;
 
const int N = 50 * 2 + 10;
const int M = (50 * 50 + 50 + 10) * 2;
const int INF = 1e9;

int n, m, S, T;
struct Edge
{
	int to, nxt, flow;
}line[M];
int fist[N], idx;
int cur[N], d[N];

void add(int x, int y, int z)
{
	line[idx] = {y, fist[x], z};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], 0};
	fist[y] = idx ++;
} 

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty()) 
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v); 
			}
		}
	} 
	return 0;
}

int find(int u, int limit)
{
	if(u == T) return limit;
	int flow = 0;	
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow)
		{
			int t = find(v, min(line[i].flow, limit - flow));
			if(!t) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		}
	}
	return flow;
}

int dinic()
{
	int res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res;
}

int main()
{
	while(scanf("%d%d", &n, &m) == 2)
	{
		memset(fist, -1, sizeof fist);
		idx = 0;
		for(int i = 0; i < n; ++ i) add(i, n + i, 1);
		for(int i = 1; i <= m; ++ i)
		{
			int a, b;
			scanf(" (%d,%d)", &a, &b);
			add(n + a, b, INF);
			add(n + b, a, INF);
		}
		int res = n;
		for(int i = 0; i < n; ++ i)
			for(int j = 0; j < i; ++ j)
			{
				S = n + i, T = j;
				for(int k = 0; k < idx; k += 2)
				{
					line[k].flow += line[k ^ 1].flow;
					line[k ^ 1].flow = 0;
				}
				res = min(res, dinic());
			}
		printf("%d
", res);
	}	
	return 0;
} 
原文地址:https://www.cnblogs.com/ooctober/p/14428071.html