网络流模板 网络流题型大荟萃

以HDU4560为例,整理了很多网络流的题目——

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<ctype.h>
#include<math.h>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<string>
#include<algorithm>
#include<time.h>
#include<bitset>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T> inline void gmax(T &a, T b) { if (b>a)a = b; }
template <class T> inline void gmin(T &a, T b) { if (b<a)a = b; }
const int N = 240, M = 14000, Z = 1e9 + 7, inf = 0x3f3f3f3f;
int casenum, casei;
int n, m, g, L;
bool e[80][80];
/*
【算法介绍】
网络流常用于解决分配匹配等问题。
其主要算法包括dinic和sap(isap)
其中,Dinic是基于层次图的网络流模型,时间复杂度为O(n ^ 2 * m)

【算法实现】
1,先通过bfs,在有流量的条件下,找到从超级源点ST到超级汇点ED的最短路
2,再通过dfs,在确保是最短路的条件下,找到一条可行的增广路
3,重复过程(1,2),直到过程1无法找到可行路径
注意:
1,网络流的实现,必然要依赖反图和反边,id = 1的初始化莫不可忘记。
2,每条单向边都对应着正边和反边,整个图的最大边数是所有单向边的数量 * 2
3,我们初始化的时候,一定要使得所有first都清零,而不只是1 ~ n的first清零
4,建图是可以慢慢化简的,如果想不到好的建图方式,可以先想拙劣建图法再分析化简

【最小割】
在网络流模型中,最大流 = 最小割。
所谓最小割, 是指用最小的成本,使得源点和汇点不连通。
最小割模型有两种可能的答案生成方式——
1,最大流就是答案
2,所有的正权,减去最大流才是答案
我们可以对对最小割生成方案,用最小割解决一些模型,或者利用最小割的思想解决问题。

【最大权闭合子图】
所谓最大权闭合子图,大概有这样的问题结构——
1,告诉你每个点的权值(权值有正有负)
2,点与点之间的选取,存在一定的依赖关系。
就比如说如果选了x,则必须要选择另外一些点{p1, p2, p3},即{px}是x的必要条件。
3,你需要决定选出若干点,进而使得点权之和最大
建图方式:
1,源点->正权点,容量为权值绝对值
2,负权点->汇点,容量为权值绝对值
3,对于如果选了x,就必须要选择y的情况,我们从x向y建立一条容量为inf的边
在这种情况下,我们跑最大流,得到最小割
与ST相连的点属于最大权闭合子图中的点,与ED相连的点不属于最大权闭合子图。
理解:
最大流 = 最小割,而最小割,意味着以最小成本,使得源点与汇点不连通。
在这种建图方式下——
割掉ST->正权点的边,意味着记下来所对应的成本太大了,我们不如放弃该正权
割掉负权点->ED的边,意味着我们虽然使用了这些成本,但是正权还没有被画完
而依赖关系之前的正无穷的边, 意味着这前驱节点和后继节点之间是不可同时割舍的
总的正权值 - 最小割, 就是问题的解

【混合图欧拉回路】
网络流可以解决混合图求欧拉回路的问题
混合图欧拉回路问题大概是这样:告诉你一个图,有些边方向是确定的,有些边方向是不确定的(需要你定向)。
要求你在定向之后,使得该图变为欧拉图。
首先我们把双向边任意定向,并对于每个点,统计其在当前定向条件下的入度和出度。
如果对于任意点,(入度 - 出度) % 2 == 1,则无解。
否则我们设(ind[i] - oud[i]) / 2 = w
对于入度>出度的点(即w > 0),我们从这个点向ED连容量为abs(w)的边使之平衡化
对于出度>入度的点(即w < 0),我们从ST向这个点连容量为abs(w)的边使之平衡化
然后跑最大流。如果最大流为∑{w, w > 0},则表示问题有解。
理解:
对于入度 > 出度的点,其向ED连了容量为abs(w)的边都要流满
如果流满了,流出的容量为出度+w = 入度-w,也就是说,我们改变1/2的入度的方向即可
对于入度 < 出度的点,其从ST连了容量为abs(w)的边都要流满
如果流满了,流入的容量为入度+w = 出度-w,也就是说,我们改变1/2的出度的方向即可
于是这种建图能对应得到问题的解。
如果要输出方案,我们把所有从x到y没有流量的边反向即可
该边流满,说明我们选择了这条边;该边未满,说明这条边需要反向
*/
int ST, ED;
int first[N], ID;
struct Edge
{
	int w, cap, nxt;
}edge[M];
void ins(int x, int y, int cap_)
{
	edge[++ID] = { y, cap_, first[x] };
	first[x] = ID;

	edge[++ID] = { x, 0, first[y] };
	first[y] = ID;
}
int d[N];
bool bfs()
{
	MS(d, -1);
	queue<int>q; q.push(ST); d[ST] = 0;
	while (!q.empty())
	{
		int x = q.front(); q.pop();
		for (int z = first[x]; z; z = edge[z].nxt)if (edge[z].cap)
		{
			int y = edge[z].w;
			if (d[y] == -1)
			{
				d[y] = d[x] + 1;
				q.push(y);
				if (y == ED)return 1;
			}
		}
	}
	return 0;
}
int dfs(int x, int all)
{
	if (x == ED)return all;
	int use = 0;
	for (int z = first[x]; z; z = edge[z].nxt)if (edge[z].cap)
	{
		int y = edge[z].w;
		if (d[y] == d[x] + 1)
		{
			int tmp = dfs(y, min(edge[z].cap, all - use));
			edge[z].cap -= tmp;
			edge[z ^ 1].cap += tmp;
			use += tmp;
			if (use == all)break;
		}
	}
	if (use == 0)d[x] = -1;
	return use;
}
int dinic()
{
	int ret = 0;
	while (bfs())ret += dfs(ST, inf);
	return ret;
}
void build(int mid)
{
	MS(first, 0); ID = 1;
	for (int i = 1; i <= m; i++)ins(n + i, n + m + i, L);
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			if (e[i][j])ins(i, n + m + j, 1);
			else ins(i, n + j, 1);
		}
	}
	ST = 0;
	ED = n + m + m + 1;
	for (int i = 1; i <= n; i++)ins(ST, i, mid);
	for (int i = 1; i <= m; i++)ins(n + m + i, ED, mid);
}
void debug()
{
	for (int x = 0; x <= ED; ++x)
	{
		for (int z = first[x]; z; z = nxt[z])if (z % 2 == 0)//正边
		{
			int y = w[z];
			if(cap[z ^ 1])printf("%d->%d(%d)\n", x, y, cap[z ^ 1]);
		}puts("");
	}
}
int main()
{
	scanf("%d", &casenum);
	for (casei = 1; casei <= casenum; casei++)
	{
		scanf("%d%d%d%d", &n, &m, &g, &L);
		MS(e, 0);
		while (g--)
		{
			int x, y; scanf("%d%d", &x, &y);
			e[x][y] = 1;
		}
		int l = 0;
		int r = m;
		while (l < r)
		{
			int mid = (l + r + 1) >> 1;
			build(mid);
			if (dinic() == mid * n)l = mid;
			else r = mid - 1;
		}
		printf("Case %d: %d\n", casei, l);
	}
	return 0;
}
/*
[题意]
本题为HDU4560
n个歌手,m种歌曲流派(n<=m<=75)
我们想要安排尽可能多的演唱会。不过有以下条件——
1,每场演唱会中,每个歌手要唱不同类型的歌曲。
2,这样可能导致有些歌手去唱他不擅长的歌曲。对于任一种歌曲,被不合适唱的次数都不能超过L。
问你最多能安排多少场演唱会。
[做法]
首先我们把烦琐的问题信息提取出来。
本题中出现了三类信息——歌手、歌曲、演唱会
我们没办法通过流量关系,使得一个演唱会在收集了n个不同的歌曲之后,向汇点
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值