以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个不同的歌曲之后,向汇点