网络流基础与应用
目录
A:POJ-3436 ACM Computer Factory
注:本篇博文全是用dinic算法写的
Dinic算法(转自Dinic)
什么是网络流
在一个有向图上选择一个源点,一个汇点,每一条边上都有一个流量上限(以下称为容量),即经过这条边的流量不能超过这个上界,同时,除源点和汇点外,所有点的入流和出流都相等,而源点只有流出的流,汇点只有汇入的流。这样的图叫做网络流。
所谓网络或容量网络指的是一个连通的赋权有向图 D= (V、E、C) , 其中V 是该图的顶点集,E是有向边(即弧)集,C是弧上的容量。此外顶点集中包括一个起点和一个终点。网络上的流就是由起点流向终点的可行流,这是定义在网络上的非负函数,它一方面受到容量的限制,另一方面除去起点和终点以外,在所有中途点要求保持流入量和流出量是平衡的。(引自百度百科)
定义
我们定义:
源点:只有流出去的点
汇点:只有流进来的点
流量:一条边上流过的流量
容量:一条边上可供流过的最大流量
残量:一条边上的容量-流量
几个基本性质
基本性质一:
对于任何一条流,总有流量<=容量
这是很显然的
基本性质二
对于任何一个不是源点或汇点的点u,总有
∑p∈Ek[p][u]==∑q∈Ek[u][q](其中k[i][j]表示i到j的流量)∑p∈Ek[p][u]==∑q∈Ek[u][q](其中k[i][j]表示i到j的流量)
这个也很显然,即一个点(除源点和汇点)的入流和出流相等
基本性质三
对于任何一条有向边(u,v),总有
k[u][v]==−k[v][u]k[u][v]==−k[v][u]
这个看起来并不是很好理解,它的意思就是一条边的反边上的流是这条边的流的相反数,可以这么想,就是如果有k[u][v]的流从u流向v,也就相当于有-k[v][u]的流从v流向u。这条性质非常重要。
网络流最大流
网络流的最大流算法就是指的一个流量的方案使得网络中流量最大。
网络流最大流的求解
网络流的所有算法都是基于一种增广路的思想,下面首先简要的说一下增广路思想,其基本步骤如下:
1.找到一条从源点到汇点的路径,使得路径上任意一条边的残量>0(注意是小于而不是小于等于,这意味着这条边还可以分配流量),这条路径便称为增广路
2.找到这条路径上最小的F[u][v](我们设F[u][v]表示u->v这条边上的残量即剩余流量),下面记为flow
3.将这条路径上的每一条有向边u->v的残量减去flow,同时对于起反向边v->u的残量加上flow(为什么呢?我们下面再讲)
4.重复上述过程,直到找不出增广路,此时我们就找到了最大流
这个算法是基于增广路定理(Augmenting Path Theorem): 网络达到最大流当且仅当残留网络中没有增广路(由于笔者知识水平不高,暂且不会证明)
举个例子:
为什么要连反向边
我们知道,当我们在寻找增广路的时候,在前面找出的不一定是最优解,如果我们在减去残量网络中正向边的同时将相对应的反向边加上对应的值,我们就相当于可以反悔从这条边流过。
比如说我们现在选择从u流向v一些流量,但是我们后面发现,如果有另外的流量从p流向v,而原来u流过来的流量可以从u->q流走,这样就可以增加总流量,其效果就相当于p->v->u->q,用图表示就是:
图中的蓝色边就是我们首次增广时选择的流量方案,而实际上如果是橘色边的话情况会更优,那么我们可以在v->u之间连一条边容量为u->v减去的容量,那我们在增广p->v->u->q的时候就相当于走了v->u这条"边",而u->v的流量就与v->u的流量相抵消,就成了中间那幅图的样子了。
如果是v->u时的流量不能完全抵消u->v的,那就说明u还可以流一部分流量到v,再从v流出,这样也是允许的。
一个小技巧
虽然说我们已经想明白了为什么要加反向边,但反向边如何具体实现呢?笔者在学习网络流的时候在这里困扰了好久,现在简要的总结在这里。
首先讲一下邻接矩阵的做法,对于G[u][v],如果我们要对其反向边进行处理,直接修改G[v][u]即可。
但有时会出现u->v和v->u同时本来就有边的情况,一种方法是加入一个新点p,使u->v,而v->u变成v->p,p->u。
另一种方法就是使用邻接表,我们把边从0开始编号,每加入一条原图中的边u->v时,加入边v->u流量设为0,那么这时对于编号为i的边u->v,我们就可以知道i^1就是其反向边v->u。
朴素算法的低效之处
虽然说我们已经知道了增广路的实现,但是单纯地这样选择可能会陷入不好的境地,比如说这个经典的例子:
我们一眼可以看出最大流是999(s->v->t)+999(s->u->t),但如果程序采取了不恰当的增广策略:s->v->u->t
我们发现中间会加一条u->v的边
而下一次增广时:
若选择了s->u->v->t
然后就变成
这是个非常低效的过程,并且当图中的999变成更大的数时,这个劣势还会更加明显。
怎么办呢?
这时我们引入Dinic算法
Dinic算法
为了解决我们上面遇到的低效方法,Dinic算法引入了一个叫做分层图的概念。具体就是对于每一个点,我们根据从源点开始的bfs序列,为每一个点分配一个深度,然后我们进行若干遍dfs寻找增广路,每一次由u推出v必须保证v的深度必须是u的深度+1。下面给出代码
一些变量的定义
int s,t;//源点和汇点
int cnt;//边的数量,从0开始编号。
int Head[maxN];//每一个点最后一条边的编号
int Next[maxM];//指向对应点的前一条边
int V[maxM];//每一条边指向的点
int W[maxM];//每一条边的残量
int Depth[maxN];//分层图中标记深度
dinic主过程
int Dinic()
{
int Ans=0;//记录最大流量
while (bfs())
{
while (int d=dfs(s,inf))
Ans+=d;
}
return Ans;
}
bfs分层图过程
bool bfs()
{
queue<int> Q;//定义一个bfs寻找分层图时的队列
while (!Q.empty())
Q.pop();
memset(Depth,0,sizeof(Depth));
Depth[s]=1;//源点深度为1
Q.push(s);
do
{
int u=Q.front();
Q.pop();
for (int i=Head[u];i!=-1;i=Next[i])
if ((W[i]>0)&&(Depth[V[i]]==0))//若该残量不为0,且V[i]还未分配深度,则给其分配深度并放入队列
{
Depth[V[i]]=Depth[u]+1;
Q.push(V[i]);
}
}
while (!Q.empty());
if (Depth[t]==0)//当汇点的深度不存在时,说明不存在分层图,同时也说明不存在增广路
return 0;
return 1;
}
dfs寻找增广路过程
int dfs(int u,int dist)//u是当前节点,dist是当前流量
{
if (u==t)//当已经到达汇点,直接返回
return dist;
for (int i=Head[u];i!=-1;i=Next[i])
{
if ((Depth[V[i]]==Depth[u]+1)&&(W[i]!=0))//注意这里要满足分层图和残量不为0两个条件
{
int di=dfs(V[i],min(dist,W[i]));//向下增广
if (di>0)//若增广成功
{
W[i]-=di;//正向边减
W[i^1]+=di;反向边加
return di;//向上传递
}
}
}
return 0;//否则说明没有增广路,返回0
}
Dinic算法的优化
Dinic算法还有优化,这个优化被称为当前弧优化,即每一次dfs增广时不从第一条边开始,而是用一个数组cur记录点u之前循环到了哪一条边,以此来加速
总代码如下,修改的地方已在代码中标出:
class Graph
{
private:
int cnt;
int Head[maxN];
int Next[maxM];
int W[maxM];
int V[maxM];
int Depth[maxN];
int cur[maxN];//cur就是记录当前点u循环到了哪一条边
public:
int s,t;
void init()
{
cnt=-1;
memset(Head,-1,sizeof(Head));
memset(Next,-1,sizeof(Next));
}
void _Add(int u,int v,int w)
{
cnt++;
Next[cnt]=Head[u];
Head[u]=cnt;
V[cnt]=v;
W[cnt]=w;
}
void Add_Edge(int u,int v,int w)
{
_Add(u,v,w);
_Add(v,u,0);
}
int dfs(int u,int flow)
{
if (u==t)
return flow;
for (int& i=cur[u];i!=-1;i=Next[i])//注意这里的&符号,这样i增加的同时也能改变cur[u]的值,达到记录当前弧的目的
{
if ((Depth[V[i]]==Depth[u]+1)&&(W[i]!=0))
{
int di=dfs(V[i],min(flow,W[i]));
if (di>0)
{
W[i]-=di;
W[i^1]+=di;
return di;
}
}
}
return 0;
}
int bfs()
{
queue<int> Q;
while (!Q.empty())
Q.pop();
memset(Depth,0,sizeof(Depth));
Depth[s]=1;
Q.push(s);
do
{
int u=Q.front();
Q.pop();
for (int i=Head[u];i!=-1;i=Next[i])
if ((Depth[V[i]]==0)&&(W[i]>0))
{
Depth[V[i]]=Depth[u]+1;
Q.push(V[i]);
}
}
while (!Q.empty());
if (Depth[t]>0)
return 1;
return 0;
}
int Dinic()
{
int Ans=0;
while (bfs())
{
for (int i=1;i<=n;i++)//每一次建立完分层图后都要把cur置为每一个点的第一条边 感谢@青衫白叙指出这里之前的一个疏漏
cur[i]=Head[i];
while (int d=dfs(s,inf))
{
Ans+=d;
}
}
return Ans;
}
};
例题
A:POJ-3436 ACM Computer Factory:(初学建议先看下面的题)题目意思:每台ACM计算机由P个部分组成,有N个机器用于组装ACM计算机,将其中若干台机器连在一起有可能能够组装成一台完整的ACM计算机。。给出每个机器的容纳组装的ACM计算机的最大数量Q,输入和输出机器的P个部分,P个部分分别用0/1/2来表示,0表示这部分不能存在,1表示这部分是必需的,2表示这部分无关紧要(存不存在都可以)。求最大的ACM计算机数量、连在一起使用的机器数、机器间传递的ACM计算机数目。思路:首先拆点,因为点上有容量限制,在拆出的两点间连一条边,容量为产量;如果输入规格中没有1,则与超级源点相连,容量为无穷大;如果输出规格中没有0,则与超级汇点相连,容量为无穷大;判断和其他机器的输入输出是否匹配,能匹配上则建边,容量为无穷大;在该图上跑一遍最大流,容量大于零的边集即为方案。(注意要输出路径)代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define INF 0x3f3f3f3f
#define LL __int64
struct node
{
int u , v , w ;
int next , k ;
} p[1000000];
struct node1
{
int u , v , w ;
} q[1000000] ;
queue <int> que ;
LL in[110] , out[110] , num[110] ;
int head[110] , cnt ;
int arr[110] , vis[110] ;
void init()
{
cnt = 0 ;
memset(in,0,sizeof(in)) ;
memset(out,0,sizeof(out)) ;
memset(head,-1,sizeof(head)) ;
}
void add(int u,int v,int w)
{
p[cnt].u = u ;
p[cnt].v = v ;
p[cnt].w = w ;
p[cnt].k = w ;
p[cnt].next = head[u] ;
head[u] = cnt++ ;
p[cnt].u = v ;
p[cnt].v = u ;
p[cnt].w = 0 ;
p[cnt].k = 0 ;
p[cnt].next = head[v] ;
head[v] = cnt++ ;
}
bool judge(LL x,LL y,int m)
{
int i , k1 , k2 ;
for(i = 1 ; i <= m ; i++)
{
k1 = x % 10 ;
k2 = y % 10 ;
x /= 10 ;
y /= 10 ;
if(k1 == 2 || k2 == 2)
continue ;
if( k1 != k2 )
return false ;
}
return true ;
}
bool bfs(int s,int e)
{
int i , u , v ;
while( !que.empty() )
que.pop();
memset(arr,-1,sizeof(arr)) ;
vis[s] = 1 ;
arr[s] = 0 ;
que.push(s) ;
while( !que.empty() )
{
u = que.front() ;
que.pop() ;
for(i = head[u] ; i != -1 ; i = p[i].next )
{
v = p[i].v ;
if( arr[v] == -1 && p[i].w )
{
arr[v] = arr[u] + 1 ;
que.push(v) ;
}
}
}
if( arr[e] > 0 )
return true ;
return false ;
}
int dfs(int u,int t,int min1)
{
if( u == t )
return min1 ;
int i , v , a , ans = 0 ;
for(i = head[u] ; i != -1 ; i = p[i].next)
{
v = p[i].v ;
if( arr[v] == arr[u] + 1 && p[i].w && ( a = dfs(v,t,min(min1,p[i].w) ) ) )
{
p[i].w -= a ;
p[i^1].w += a ;
ans += a ;
min1 -= a ;
if( !min1 ) break ;
}
}
if( ans )
return ans ;
arr[u] = -1 ;
return 0 ;
}
int main()
{
int n , m , i , j , u , v , w , k , max_flow , sum ;
while( scanf("%d %d", &m, &n) != EOF )
{
init() ;
max_flow = sum = 0 ;
for(i = 1 ; i <= n ; i++)
{
k = 0 ;
scanf("%I64d", &num[i]) ;
for(j = 0 ; j < m ; j++)
{
scanf("%d", &w) ;
in[i] = in[i]*10 + w ;
k = k*10 + 1 ;
}
for(j = 0 ; j < m ; j++)
{
scanf("%d", &w) ;
out[i] = out[i]*10 + w ;
}
}
for(i = 1 ; i <= n ; i++)
{
add(i,i+50,num[i]) ;
if( judge(0,in[i],m) )
add(0,i,INF) ;
if( judge(k,out[i],m) )
add(i+50,101,INF) ;
for(j = 1 ; j <= n ; j++)
{
if( i == j ) continue ;
if( judge(out[i],in[j],m) )
add(i+50,j,INF) ;
}
}
while( bfs(0,101) )
{
while( k = dfs(0,101,INF) )
max_flow += k ;
}
for(i = 51 ; i <= n+50 ; i++)
{
for(j = head[i] ; j != -1 ; j = p[j].next)
{
if( p[j].u != 0 && p[j].v != 101 && p[j].w < p[j].k )
{
q[sum].u = i-50 ;
q[sum].v = p[j].v ;
q[sum].w = p[j].k - p[j].w ;
sum++ ;
}
}
}
if( max_flow == 0 )
printf("0 0\n") ;
else
{
printf("%d %d\n", max_flow, sum) ;
for(i = 0 ; i < sum ; i++)
printf("%d %d %d\n", q[i].u, q[i].v, q[i].w) ;
}
}
return 0;
}
B:POJ-3281 Dining:题意:有N头牛,F种食物,D种饮料,每头牛都有自己喜欢的食物和饮料,每种饮料和食物只能分配给一头牛。问:最多有多少头牛能同时得到自己喜欢的食物和饮料。思路(先上一个盗的图):![](https://img-blog.csdnimg.cn/2018113010520373.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0Fkb2xwaHJvY3M=,size_16,color_FFFFFF,t_70)
首先要保证牛只能获得一种食物和饮料,所以要把牛分成两个点(不分的话流过牛的食物就会无穷多了)而食物和饮料如果放在一边就实现不了(大家可以仔细想想,我也解释不了O(∩_∩)O)。接下来就是建图套模板了,代码:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <set>
#include <map>
#include <stack>
#include <queue>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 1005;
const int maxm = 1000005;
int n, m;//点数、边数
int sp, tp;//原点、汇点
struct node {
int u;
int v, next;
int cap;
}mp[maxm];
int pre[maxn], dis[maxn], cur[maxn];//cur为当前弧优化,dis存储分层图中每个点的层数(即到原点的最短距离),pre建邻接表
int cnt = 0;
void init() { //不要忘记初始化
cnt = 0;
memset(pre, -1, sizeof(pre));
}
void add(int u, int v, int w) { //加边
mp[cnt].u = u;
mp[cnt].v = v;
mp[cnt].cap = w;
mp[cnt].next = pre[u];
pre[u] = cnt++;
mp[cnt].u = v;
mp[cnt].v = u;
mp[cnt].cap = 0;
mp[cnt].next = pre[v];
pre[v] = cnt++;
}
bool bfs() { //建分层图
memset(dis, -1, sizeof(dis));
queue<int>q;
while(!q.empty())
q.pop();
q.push(sp);
dis[sp] = 0;
int u, v;
while(!q.empty()) {
u = q.front();
q.pop();
for(int i = pre[u]; i != -1; i = mp[i].next) {
v = mp[i].v;
if(dis[v] == -1 && mp[i].cap > 0) {
dis[v] = dis[u] + 1;
q.push(v);
if(v == tp)
break;
}
}
}
return dis[tp] != -1;
}
int dfs(int u, int cap) {//寻找增广路
if(u == tp || cap == 0)
return cap;
int res = 0, f;
for(int &i = cur[u]; i != -1; i = mp[i].next) {//
int v = mp[i].v;
if(dis[v] == dis[u] + 1 && (f = dfs(v, min(cap - res, mp[i].cap))) > 0) {
mp[i].cap -= f;
mp[i ^ 1].cap += f;
res += f;
if(res == cap)
return cap;
}
}
if(!res)
dis[u] = -1;
return res;
}
int dinic() {
int ans = 0;
while(bfs()) {
for(int i = sp; i <= tp; i++)
cur[i] = pre[i];
ans += dfs(sp, inf);
}
return ans;
}
int main()
{
int f, d;
while(~scanf("%d %d %d", &n, &f, &d)) {
init();
sp = 0;
tp = f + n + n + d + 1;
for(int i = 1; i <= f; i++) {
add(sp, i, 1);
}
for(int i = 1; i <= d; i++) {
add(f + n + n + i, tp, 1);
}
int fi, di;
for(int i = 1; i <= n; i++) {
add(f + i, f + n + i, 1);
scanf("%d %d", &fi, &di);
int k;
for(int j = 1; j <= fi; j++) {
scanf("%d", &k);
add(k, f + i, 1);
}
for(int j = 1; j <= di; j++) {
scanf("%d", &k);
add(f + n + i, f + n + n + k, 1);
}
}
int ans = dinic();
printf("%d\n", ans);
}
return 0;
}
C:POJ-1087 A Plug for UNIX:题意:在一个会议室里有n种插座,每种插座一个,每个插座只能插一种以及一个电器(或者适配器),有m个电器,每个电器有一个插头需要插在相应一种插座上,不是所有电器都能在会议室找到相应插座,有k种适配器,每种适配器可以有无限多数量,每种适配器(a, b)可以把b类插座变为a类插座,问最后有多少个电器无法使用。解题思路:网络流,问题是怎样建图。我的做法是将0做为起点s,与所有的插座建立一条边,容量为1,然后再将所有的电器与终点t建立一条边,容量为1;然后将转接器所桥接的插头与插座的类型间建立一条边,容量为1。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <map>
#define maxn 10000
#define maxm 1000000
#define INF 0x3f3f3f3f
using namespace std;
int head[maxn], cur[maxn], cnt;
int dist[maxn], vis[maxn];
int n;// 插座个数
int m;// 电器个数
int k;// 转换器个数
int sect; //超级汇点
struct node{
int u, v, cap, flow, next;
};
node edge[maxm];
map<string, int>mp;
void init(){
cnt = 0;
memset(head, -1, sizeof(head));
mp.clear();
}
void add(int u, int v, int w){
edge[cnt] = {u, v, w, 0, head[u]};
head[u] = cnt++;
edge[cnt] = {v, u, 0, 0, head[v]};
head[v] = cnt++;
}
void getmap(){
char s[30];
char str[30];
int ans = 1;
while(n--){
scanf("%s", s);
mp[s] = ans++;
add(0, mp[s], 1); // 0为超级源点
}
scanf("%d", &m);
for(int i = 0 ; i < m; ++i){
scanf("%s%s", str, s);
mp[str] = ans++;
if(!mp[s]){
mp[s] = ans++;
}
add(mp[s], mp[str], 1);
add(mp[str], sect, 1);
}
scanf("%d", &k);
while(k--){
scanf("%s%s", s, str);
if(!mp[s])
mp[s] = ans++;
if(!mp[str])
mp[str] = ans++;
add(mp[str], mp[s], INF);//后一个插座向前一个插座建边
}
}
bool BFS(int st, int ed){
queue<int>q;
memset(vis, 0, sizeof(vis));
memset(dist, -1, sizeof(dist));
vis[st] = 1;
dist[st] = 0;
q.push(st);
while(!q.empty()){
int u = q.front();
q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
node E = edge[i];
if(!vis[E.v] && E.cap > E.flow){
vis[E.v] = 1;
dist[E.v] = dist[u] + 1;
if(E.v == ed) return true;
q.push(E.v);
}
}
}
return false;
}
int DFS(int x, int ed, int a){
if(x == ed || a == 0)
return a;
int flow = 0, f;
for(int &i = cur[x]; i != -1; i = edge[i].next){
node &E = edge[i];
if(dist[E.v] == dist[x] + 1 && (f = DFS(E.v, ed, min(a, E.cap - E.flow))) > 0){
E.flow += f;
edge[i ^ 1].flow -= f;
a -= f;
flow += f;
if(a == 0) break;
}
}
return flow;
}
int maxflow(int st, int ed){
int flowsum = 0;
while(BFS(st, ed)){
memcpy(cur, head, sizeof(head));
flowsum += DFS(st, ed, INF);
}
return flowsum;
}
int main(){
while(scanf("%d", &n)!=EOF){
sect = n + m + k + 1;
init();
getmap();
int sum;
sum = maxflow(0 , sect);
printf("%d\n", m - sum);
}
return 0;
}
D:POJ-2195 Going Home:(最小费用最大流问题)算是一个本类题的模板。题目大意:给定一个N*M的地图,地图上有若干个man和house,且man与house的数量一致。man每移动一格需花费1(即单位费用=单位距离),一间house只能入住一个man。现在要求所有的man都入住house,求最小费用。 本题建图很简单,(其他为模板)直接上代码理解:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<cmath>
#include<queue>
#define INF 1<<29
#define N 310111
#define ll long long
using namespace std;
struct Edge {
int to,next,cap,flow,cost;
} edge[N];
struct node {
int x,y;
} H[N],M[N];
int lh,lm;
int head[N],tol;
int pre[N],dis[N];
bool vis[N];
int n,m;
int E;
int cost;
void init(int t) {
E=t;
tol=0;
memset(head,-1,sizeof head);
}
int dist(node a,node b) {
return abs(a.x-b.x)+abs(a.y-b.y);
}
void addedge(int u,int v,int cap,int cost) {
edge[tol].to=v;
edge[tol].cap=cap;
edge[tol].cost=cost;
edge[tol].flow=0;
edge[tol].next=head[u];
head[u]=tol++;
edge[tol].to=u;
edge[tol].cap=0;
edge[tol].cost=-cost;
edge[tol].flow=0;
edge[tol].next=head[v];
head[v]=tol++;
}
bool spfa(int s,int t) {
queue<int>q;
for(int i = 0; i < E; i++) {
dis[i] = INF;
vis[i] = false;
pre[i] = -1;
}
dis[s] = 0;
vis[s] = true;
q.push(s);
while(!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for(int i = head[u]; i != -1; i = edge[i]. next) {
int v = edge[i]. to;
if(edge[i].cap > edge[i].flow &&
dis[v] > dis[u] + edge[i]. cost ) {
dis[v] = dis[u] + edge[i]. cost;
pre[v] = i;
if(!vis[v]) {
vis[v] = true;
q.push(v);
}
}
}
}
if(pre[t] == -1)return false;
else return true;
}
//返回的是最大流,cost存的是最小费用
int minCostMaxflow(int s,int t,int &cost) {
int flow = 0;
cost = 0;
while(spfa(s,t)) {
int Min = INF;
for(int i = pre[t]; i != -1; i = pre[edge[i^1].to]) {
if(Min > edge[i].cap - edge[i]. flow)
Min = edge[i].cap - edge[i].flow;
}
for(int i = pre[t]; i != -1; i = pre[edge[i^1].to]) {
edge[i].flow += Min;
edge[i^1].flow -= Min;
cost += edge[i]. cost * Min;
}
flow += Min;
}
return flow;
}
int main() {
//freopen("in.txt","r",stdin);
while(~scanf("%d%d",&n,&m),n+m) {
char c[200];
lh=0;
lm=0;
for(int i=0; i<n; i++) {
scanf("%s",c);
for(int j=0; j<m; j++) {
if(c[j]=='H') {
H[lh].x=i;
H[lh].y=j;
lh++;
} else if(c[j]=='m') {
M[lm].x=i;
M[lm].y=j;
lm++;
}
}
}
init(lh+lm+2);
for(int i=0; i<lh; i++)
for(int j=0; j<lm; j++) {
addedge(i,lh+j,1,dist(H[i],M[j]));
}
for(int i=0; i<lh; i++) {
addedge(lh+lm,i,1,0);//lh+lm为超级源点,lh+lm+1为超级汇点
}
for(int j=0; j<lm; j++) {
addedge(lh+j,lh+lm+1,1,0);
}
int cost;
int res=minCostMaxflow(lm+lh,lm+lh+1,cost);
printf("%d\n",cost);
}
return 0;
}
E:POJ-2516 Minimum Cost:题意:
思路:如果只有一种商品那就是最普通的费用流问题了,现在变成了k种商品,那我们就求k次费用流相加就可以了。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <cmath>
#define INF 1<<29
#define N 33333
using namespace std;
struct Edge {
int to,next,cap,flow,cost;
} edge[N];
int ned[70][70],gong[70][70],co[70][70];
int head[N],tol;
int pre[N],dis[N];
bool vis[N];
int sum[N];
int n,m,k;
int E;
void init(int t) {
E=t;
tol=0;
memset(head,-1,sizeof head);
}
void addedge(int u,int v,int cap,int cost) {
edge[tol].to=v;
edge[tol].cap=cap;
edge[tol].cost=cost;
edge[tol].flow=0;
edge[tol].next=head[u];
head[u]=tol++;
edge[tol].to=u;
edge[tol].cap=0;
edge[tol].cost=-cost;
edge[tol].flow=0;
edge[tol].next=head[v];
head[v]=tol++;
}
bool spfa(int s,int t) {
queue<int>q;
for(int i = 0; i < E; i++) {
dis[i] = INF;
vis[i] = false;
pre[i] = -1;
}
dis[s] = 0;
vis[s] = true;
q.push(s);
while(!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for(int i = head[u]; i != -1; i = edge[i]. next) {
int v = edge[i]. to;
if(edge[i].cap > edge[i].flow &&
dis[v] > dis[u] + edge[i]. cost ) {
dis[v] = dis[u] + edge[i]. cost;
pre[v] = i;
if(!vis[v]) {
vis[v] = true;
q.push(v);
}
}
}
}
if(pre[t] == -1)return false;
else return true;
}
//返回的是最大流,cost存的是最小费用
int minCostMaxflow(int s,int t,int &cost) {
int flow = 0;
cost = 0;
while(spfa(s,t)) {
int Min = INF;
for(int i = pre[t]; i != -1; i = pre[edge[i^1].to]) {
if(Min > edge[i].cap - edge[i]. flow)
Min = edge[i].cap - edge[i].flow;
}
for(int i = pre[t]; i != -1; i = pre[edge[i^1].to]) {
edge[i].flow += Min;
edge[i^1].flow -= Min;
cost += edge[i]. cost * Min;
}
flow += Min;
}
return flow;
}
int main() {
//freopen("test.in","r",stdin);
while(~scanf("%d%d%d",&n,&m,&k),n+m+k) {
memset(sum,0,sizeof sum);
for(int i=0; i<n; i++) {
for(int j=0; j<k; j++) {
scanf("%d",&ned[i][j]);
sum[j]+=ned[i][j];
}
}
for(int i=0; i<m; i++) {
for(int j=0; j<k; j++) {
scanf("%d",&gong[i][j]);
}
}
int flag=1;
int ans=0;
for(int kk=0; kk<k; kk++) {
for(int i=0; i<n; i++) {
for(int j=0; j<m; j++) {
scanf("%d",&co[i][j]);
}
}
if(flag) {
init(n+m+3);
int s=n+m;
int t=s+1;
int cost;
for(int i=0; i<n; i++) {//ned和gong之间见图
for(int j=0; j<m; j++) {
addedge(i,n+j,INF,co[i][j]);///因为ned和gong之间是必通的,容量置为INF
}
}
for(int i=0; i<n; i++)addedge(s,i,ned[i][kk],0);///s和ned
for(int j=0; j<m; j++)addedge(j+n,t,gong[j][kk],0);///gong和t
int res=minCostMaxflow(s,t,cost);
if(res!=sum[kk])flag=0;///最终该物品满足所有店需求是res==sum[kk]
ans+=cost;
}
}
if(flag)
printf("%d\n",ans);
else
printf("-1\n");
}
return 0;
}
F:POJ-1459 Power Network:题意:多组测试数据,每组测试数据给出N,Np,Nc,M。N代表结点数量,M代表边的数量,Np代表结点中电站的数量(只产电,不耗电),Nc代表消费者数量(只耗电,不产电),其他结点代表中转站(不产电,不耗电)。 接着M组(u,v)w数据,代表u到v容量为w,接着Np组(u)w,代表结点u产电w,接着Nc组(u)w,代表结点u耗电w。 求电网中能消耗的最大电能值。思路:源点设为0,汇点设为N+1,鉴于u>=0,v>=0,构图时可以map[u+1][v+1]=w。源点到产电站的容量看成产电量,消费者到汇点的容量看作消费者的耗电量,直接求流入汇点的最大流即可。
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
#define inf 1<<29
int n,np,nc,m;
int G[105][105];
int level[105];
int bfs(int s,int t)
{
memset(level,0,sizeof level);
queue<int> q;
level[s]=1;
q.push(s);
while(!q.empty())
{
int top=q.front(); q.pop();
for(int i=0;i<=n+1;i++)
{
if(!level[i] && G[top][i])
{
level[i]=level[top]+1;
q.push(i);
}
}
}
return level[t];
}
int dfs(int u,int t,int flow)
{
int s=flow,increase;
if(u==t) return flow;
for(int i=0;i<=n+1;i++)
{
if(level[i]==level[u]+1 && G[u][i])
{
increase=dfs(i,t,min(flow,G[u][i]));
G[u][i]-=increase;
G[i][u]+=increase;
flow-=increase;
}
}
return s-flow;
}
int dinic(int s,int t)
{
int ans=0;
while(bfs(s,t)) ans+=dfs(s,t,inf);
return ans;
}
int main()
{
while(~scanf("%d%d%d%d",&n,&np,&nc,&m))
{
char str[100];
memset(G,0,sizeof G);
int u,v,w,point;
for(int i=0;i<m;i++)
{
scanf("%s",str);
point=strchr(str,',')-str;
sscanf(str+1,"%d",&u);
sscanf(str+point+1,"%d",&v);
point=strchr(str,')')-str;
sscanf(str+point+1,"%d",&w);
G[++u][++v]+=w;
}
for(int i=0;i<np;i++)
{
scanf("%s",str);
sscanf(str+1,"%d",&u);
point=strchr(str,')')-str;
sscanf(str+point+1,"%d",&w);
G[0][++u]+=w;
}
for(int i=0;i<nc;i++)
{
scanf("%s",str);
sscanf(str+1,"%d",&u);
point=strchr(str,')')-str;
sscanf(str+point+1,"%d",&w);
G[++u][n+1]+=w;
}
printf("%d\n",dinic(0,n+1));
}
return 0;
}
G:HDU-4280 Island Transport:题意:n个岛屿有m个航道,每个航道能运送一定的人,问从最左端的岛屿到最右端的岛屿能运送的最大人流量(双向图),简单的最大流问题,代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=100005;
const int maxx=200005;
struct node
{
int v,w,next;
}e[maxx];
int head[maxn],vis[maxn];
int tot,S,T;
void addEdge(int u,int v,int cap)
{
e[tot].v=v,e[tot].w=cap,e[tot].next=head[u],head[u]=tot++;
e[tot].v=u,e[tot].w=cap,e[tot].next=head[v],head[v]=tot++;
}
bool bfs()
{
queue<int> q;
memset(vis,-1,sizeof(vis));
q.push(S);
vis[S]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];~i;i=e[i].next)
{
int v=e[i].v;
if(vis[v]==-1&&e[i].w)
{
vis[v]=vis[u]+1;
if(v==T)
return true;
q.push(v);
}
}
}
return false;
}
int dfs(int u,int f)
{
if(u==T||!f)
return f;
int r=0;
for(int i=head[u];~i;i=e[i].next)
{
int v=e[i].v;
if(vis[v]==vis[u]+1&&e[i].w)
{
int d=dfs(v,min(f,e[i].w));
if(d>0)
{
e[i].w-=d;
e[i^1].w+=d;
r+=d;
f-=d;
if(!f)
break;
}
}
}
if(!r)
vis[u]=-1;
return r;
}
int Dinic()//然后直接调用这个即可
{
int ans=0;
while(bfs())
ans+=dfs(S,INF);
return ans;
}
void init()//记得每次使用前初始化
{
memset(head,-1,sizeof(head));
tot=0;
}
int main()
{
int t;
scanf("%d",&t);
int x,y,w;
int n,m;
int maxI,minI;
while(t--)
{
init();
scanf("%d%d",&n,&m);
scanf("%d%d",&x,&y);
S=T=1;
maxI=minI=x;
for(int i=2;i<=n;i++)
{
scanf("%d%d",&x,&y);
if(maxI<x)
T=i,maxI=x;
if(minI>x)
S=i,minI=x;
}
while(m--)
{
scanf("%d%d%d",&x,&y,&w);
addEdge(x,y,w);
}
int ans=Dinic();
printf("%d\n",ans);
}
return 0;
}
H:HDU-4292 Food:题意:有N个人,准备了F种食物和D种饮料,并给定每种食物和饮料的数量,每个人都有喜欢的食物和饮料,这些食物和饮料最多能满足多少人。思路:网络流,添加超级源点和食物相连,边权为该食物的数量,添加超级汇点和饮料相连,边权为该种饮料的数量,将人拆点,边权为1,建图,s->食物->人->人->饮料->e。
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<map>
//#include<set>
#include<deque>
#include<queue>
#include<stack>
#include<bitset>
#include<string>
#include<fstream>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define mod 998244353
#define clean(a,b) memset(a,b,sizeof(a))// 水印
struct node{
int v,w,nxt;
node(int _v=0,int _w=0,int _nxt=0):
v(_v),w(_w),nxt(_nxt){}
}edge[1000005];
int head[1000005],e;
int dis[1000005];
int n,f,d;
int s,t;
void add(int u,int v,int w)
{
edge[e]=node(v,w,head[u]);
head[u]=e++;
edge[e]=node(u,0,head[v]);
head[v]=e++;
}
bool bfs()
{
clean(dis,-1);
dis[s]=0;
queue<int> que;
que.push(s);
while(que.size())
{
int u=que.front();
que.pop();
if(u==t)
return 1;
for(int i=head[u];i+1;i=edge[i].nxt)
{
int temp=edge[i].v;
if(dis[temp]<0&&edge[i].w>0)
{
dis[temp]=dis[u]+1;
que.push(temp);
}
}
}
return 0;
}
int dfs(int u,int low)
{
if(u==t)
return low;
int res=0;
for(int i=head[u];i+1;i=edge[i].nxt)
{
int temp=edge[i].v;
if(dis[temp]==dis[u]+1&&edge[i].w>0)
{
int d=dfs(temp,min(low-res,edge[i].w));
edge[i].w-=d;
edge[i^1].w+=d;
res+=d;
if(res==low)
return res;
}
}
return res;
}
void dinic()
{
int ans=0,res;
while(bfs())
{
while(res=dfs(s,INF))
ans+=res;
}
printf("%d\n",ans);
}
void intt()
{
e=0;
clean(head,-1);
}
int main()
{
while(scanf("%d%d%d",&n,&f,&d)!=EOF)
{
intt();
s=0,t=4*200+1;
int num;
for(int i=1;i<=f;++i)
{
scanf("%d",&num);
add(s,i,num);
}
for(int i=1;i<=d;++i)
{
scanf("%d",&num);
add(i+3*200,t,num);
}
char str[210];
for(int i=1;i<=n;++i)
{
clean(str,'\0');
scanf("%s",str);
int l=strlen(str);
for(int j=1;j<=l;++j)
{
if(str[j-1]=='Y')
add(j,i+200,1);
}
}
for(int i=1;i<=n;++i)
{
clean(str,'\0');
scanf("%s",str);
int l=strlen(str);
for(int j=1;j<=l;++j)
{
if(str[j-1]=='Y')
add(i+200*2,j+3*200,1);
}
}
for(int i=1;i<=n;++i)
add(i+200,i+2*200,1);
dinic();
}
}
I:HDU-4289 Control:最大流最小割问题,首先要明白最大流即为最小割。题目大意:给你n个点,m条无向边,给你起点和终点,以及拆除每个城市所需要的花费,点权,然后m条无向边给出,问你如何拆除城市,使得从起点无法走到终点,问这个最小花费。经典最大流最小割问题,建图挺简单的,直接上代码:
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#define INF 0x3f3f3f3f
using namespace std;
#define MAXN 100005
#define MAXM 2005
struct node
{
int v,next,flow;
} edge[MAXN];
int index,head[MAXN];
void add_edge(int u, int v, int flow)
{
edge[index].v=v;
edge[index].flow=flow;
edge[index].next=head[u];
head[u]=index++;
edge[index].v=u;
edge[index].flow=0;
edge[index].next=head[v];
head[v]=index++;
}
int deep[MAXN];
bool search_(int s,int t)
{
memset(deep,0,sizeof(deep));
queue<int> q;
q.push(s);
deep[s]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u]; i!=-1; i=edge[i].next)
{
int v=edge[i].v;
if(!deep[v]&&edge[i].flow)
{
deep[v]=deep[u]+1;
q.push(v);
if(v==t)return 1;
}
}
}
return 0;
}
int DFS(int s,int t,int min_)
{
if(s==t)return min_;
int i,ans=0,f;
for(i=head[s]; i!=-1 &&ans<min_; i=edge[i].next)
{
int v=edge[i].v;
if(deep[v]==deep[s]+1 && edge[i].flow)
{
f=DFS(v,t,min(min_-ans,edge[i].flow));
edge[i].flow-=f;
edge[i^1].flow+=f;
ans+=f;
if(ans==min_)return ans;
}
}
return ans;
}
int dinic(int s,int t)
{
int ans=0,p;
while(search_(s,t))
{
while(p=DFS(s,t,INF))
ans+=p;
}
return ans;
}
int food[MAXM],drink[MAXM];
char str[MAXM];
int main()
{
int n,m,a,b;
int s,t;
while(scanf("%d%d",&n,&m)!=EOF)
{
index=0;
memset(head,-1,sizeof(head));
scanf("%d%d",&s,&t);
for(int i=1; i<=n; ++i)
{
scanf("%d",&a);
add_edge(i,i+n,a);
}
for(int i=0; i<m; ++i)
{
scanf("%d%d",&a,&b);
add_edge(a+n,b,INF);
add_edge(b+n,a,INF);
}
int ans=dinic(s,t+n);
printf("%d\n",ans);
}
return 0;
}
J:UVA-10480 Sabotage:题意:n个点,m条边,给你每条边割掉的权值,问让1,2两点走不通且用最小花费,应该怎么割,输出割的边,其实就是最小割的问题。怎么求出最小割的边:在求出最大流之后,残余网络会分成两个部分,和源点相连的是一个集合,和汇点相连的是另一个集合,然后用a表示从源点到其他各点的最大流,在求出最大流之后,a>0 的就在源点集合中,反之为0的就在汇点集合中。剩下的就是简单的建图问题:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int maxm=10010;
const int maxn=1010;
const int inf=0x3f3f3f3f;
struct Node
{
int from;
int to;
int capa;
int next;
}edge[maxm];
struct LDJ
{
int x,y;
}point[maxn];
int cnt;
int source,sink;
int head[maxn];
int dep[maxn];
bool vis[maxn];
bool cut_vis[maxn];
void init()
{
memset(head,-1,sizeof(head));
memset(cut_vis,false,sizeof(cut_vis));
cnt=0;
return;
}
void add(int u,int v,int capa)
{
edge[cnt].from=u;
edge[cnt].to=v;
edge[cnt].capa=capa;
edge[cnt].next=head[u];
head[u]=cnt++;
edge[cnt].from=v;
edge[cnt].to=u;
edge[cnt].capa=capa;
edge[cnt].next=head[v];
head[v]=cnt++;
return;
}
bool bfs()
{
queue<int> que;
que.push(source);
memset(dep,-1,sizeof(dep));
dep[source]=0;
while(!que.empty())
{
int node=que.front();
que.pop();
for(int i=head[node];~i;i=edge[i].next)
{
int v=edge[i].to;
if(edge[i].capa>0&&dep[v]==-1)
{
dep[v]=dep[node]+1;
if(v==sink) return true;
que.push(v);
}
}
}
return dep[sink]!=-1;
}
int dfs(int node,int minn)
{
if(node==sink||minn==0)
{
return minn;
}
int r=0;
for(int i=head[node];~i;i=edge[i].next)
{
int v=edge[i].to;
if(edge[i].capa>0&&dep[v]==dep[node]+1)
{
int tmp=dfs(v,min(edge[i].capa,minn));
if(tmp>0)
{
edge[i].capa-=tmp;
edge[i^1].capa+=tmp;
r+=tmp;
minn-=tmp;
if(!minn) break;
}
}
}
if(!r) dep[node]=-1;
return r;
}
int dinic()
{
int maxflow=0;
while(bfs())
{
maxflow+=dfs(source,inf);
}
return maxflow;
}
void cut_dfs(int node)
{
cut_vis[node]=true;
for(int i=head[node];~i;i=edge[i].next)
{
int v=edge[i].to;
if(edge[i].capa>0&&!cut_vis[v])
{
cut_dfs(v);
}
}
return;
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n,m;
while(~scanf("%d%d",&n,&m))
{
if(!n&&!m) break;
init();
for(int i=0;i<m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
point[i].x=u;
point[i].y=v;
add(u,v,w);
}
source=1;
sink=2;
int ans=dinic();
cut_dfs(source);
for(int i=0;i<m;i++)
{
int u=point[i].x;
int v=point[i].y;
if((cut_vis[u]&&!cut_vis[v])||(!cut_vis[u]&&cut_vis[v]))
{
cout<<u<<' '<<v<<endl;
}
}
puts("");
}
return 0;
}
K:HDU-2732 Leapin' Lizards:题意:给一个n行的图(不知道有多少列),每个点的数字表示能承受的最大跳跃次数,接下来又是n行,‘L’表示蜥蜴所在地方,给出蜥蜴能跳跃的最大距离d,每次蜥蜴跳离的那根柱子的承受力会减一,若为零了该点的柱子会塌,现在问最少有多少蜥蜴跳不出去。 思路:首先将有柱子的点拆成两个,权为承受力,增加超级源点和汇点,‘L’和源点相连,权为1,能一次跳出去的柱子和汇点相连,权为INF,然后能相互到达的柱子之间连边,权为INF。这一题要注意蜥蜴能跳的不只是四个方向,360度任何方向都行,只要两个柱子之间的距离小于d。最后注意输出。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <string>
#include <map>
#include <stack>
#include <vector>
#include <set>
#include <queue>
#pragma comment (linker,"/STACK:102400000,102400000")
#define maxn 10005
#define MAXN 50005
#define mod 1000000009
#define INF 0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-6
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define FRE(i,a,b) for(i = a; i <= b; i++)
#define FREE(i,a,b) for(i = a; i >= b; i--)
#define FRL(i,a,b) for(i = a; i < b; i++)
#define FRLL(i,a,b) for(i = a; i > b; i--)
#define mem(t, v) memset ((t) , v, sizeof(t))
#define sf(n) scanf("%d", &n)
#define sff(a,b) scanf("%d %d", &a, &b)
#define sfff(a,b,c) scanf("%d %d %d", &a, &b, &c)
#define pf printf
#define DBG pf("Hi\n")
typedef long long ll;
using namespace std;
struct Edge{
int u,v,cap,next;
}edge[MAXN];
int n,d;
int dir[4][2]={0,1,0,-1,-1,0,1,0};
int head[maxn],level[maxn],cur[maxn];
int num,cnt,all;
char MP[25][25];
int mp[25][25];
int number[25][25];
void init()
{
cnt=0;
num=0;
all=0;
mem(head,-1);
}
void addedge(int u,int v,int w)
{
edge[num].u=u; edge[num].v=v; edge[num].cap=w; edge[num].next=head[u]; head[u]=num++;
edge[num].u=v; edge[num].v=u; edge[num].cap=0; edge[num].next=head[v]; head[v]=num++;
}
bool bfs(int s,int t)
{
mem(level,-1);
queue<int>Q;
level[s]=0;
Q.push(s);
while (!Q.empty())
{
int u=Q.front(); Q.pop();
for (int i=head[u];i+1;i=edge[i].next)
{
int v=edge[i].v;
if (level[v]==-1&&edge[i].cap>0)
{
level[v]=level[u]+1;
Q.push(v);
}
}
}
return level[t]!=-1;
}
int dfs(int u,int t,int f)
{
if (u==t) return f;
for (int &i=cur[u];i+1;i=edge[i].next)
{
int v=edge[i].v;
if (edge[i].cap>0&&level[v]==level[u]+1)
{
int d=dfs(v,t,min(f,edge[i].cap));
if (d>0)
{
edge[i].cap-=d;
edge[i^1].cap+=d;
return d;
}
}
}
return 0;
}
int dinic(int s,int t,int cnt)
{
int flow=0;
while (bfs(s,t))
{
for (int i=0;i<=cnt;i++) cur[i]=head[i];
int f;
while ((f=dfs(s,t,INF))>0)
flow+=f;
}
return flow;
}
int main()
{
int i,j,k,p,t,cas=0;
sf(t);
while (t--)
{
init();
int len;
sff(n,d);
for (i=0;i<n;i++)
{
scanf("%s",MP[i]);
len=strlen(MP[i]);
for (j=0;j<len;j++)
{
mp[i][j]=MP[i][j]-'0';
number[i][j]=++cnt;
}
}
mem(MP,0);
for (i=0;i<n;i++)
scanf("%s",MP[i]);
int t=2*cnt+1;
for (i=0;i<n;i++)
{
for (j=0;j<len;j++)
{
if (mp[i][j])
{
if (i<d||j<d||n-i<=d||len-j<=d)
addedge(number[i][j]+cnt,t,INF);
addedge(number[i][j],number[i][j]+cnt,mp[i][j]); //拆点
for (k=0;k<n;k++)
{
for (p=0;p<len;p++)
{
int dx=abs(i-k);
int dy=abs(j-p);
double mm=sqrt(dx*dx*1.0+dy*dy*1.0);
if (mm>d) continue;
addedge(number[i][j]+cnt,number[k][p],INF);
}
}
}
if (MP[i][j]=='L')
{
addedge(0,number[i][j],1);
all++;
}
}
}
int s=dinic(0,t,t+1);
int ans=all-s;
if (!ans)
printf("Case #%d: no lizard was left behind.\n",++cas);
else if (ans==1)
printf("Case #%d: 1 lizard was left behind.\n",++cas);
else
printf("Case #%d: %d lizards were left behind.\n",++cas,ans);
}
return 0;
}
L:HDU-3605 Escape:(缩点+网络流之最大流)题意:N个人要要到M个星球上去,告诉每个人可以去哪些星球,以及每个 星球可以住的人数,问所有的人是否都可以安排完 。由于n的数据范围太大,普通的建图方法会超时超内存,需要缩点,因为对于每个点来说,一共只有2^10种方法,而最多一共有10W个点,显然有很多点是重复的,这时可以采取缩点的方法,将重复的当成一个点来处理。这样数据范围就缩小到了1024个点,速度大大提升。把每类人与其所能适应的星球连边, 容量为INF S与每类人连边, 容量为这类人的人数 每个星球与T连边, 容量为星球容量。AC代码:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <map>
using namespace std;
const int MAXV = 3000, INF = 0X3F3F3F3F;
struct edge
{
int to, cap, rev;
edge(int a, int b, int c) :to(a), cap(b), rev(c){}
};
vector<edge> G[MAXV];
int iter[MAXV], level[MAXV], S, T;
void add_edge(int from, int to, int cap)
{
G[from].push_back(edge(to, cap, G[to].size()));
G[to].push_back(edge(from, 0, G[from].size()-1));
}
bool bfs()
{
memset(level, -1, sizeof(level));
level[S] = 0;
queue<int> que;
que.push(S);
while(!que.empty())
{
int v = que.front(); que.pop();
for(int i=0; i<(int)G[v].size(); ++i)
{
edge &e = G[v][i];
if(e.cap>0 && level[e.to]==-1)
{
level[e.to] = level[v] + 1;
que.push(e.to);
}
}
}
return level[T]!=-1;
}
int dfs(int v, int f)
{
if(v == T) return f;
for(int &i=iter[v]; i<(int)G[v].size(); ++i)
{
edge &e = G[v][i];
if(e.cap>0 && level[e.to]>level[v])
{
int d = dfs(e.to, min(f, e.cap));
if(d)
{
e.cap -= d;
G[e.to][e.rev].cap+= d;
return d;
}
}
}
return 0;
}
int max_flow()
{
int flow = 0;
while(bfs())
{
memset(iter, 0, sizeof(iter));
int f;
while((f=dfs(S, INF))) flow += f;
}
return flow;
}
void init()
{
for(int i=0; i<MAXV; ++i) G[i].clear();
}
int n, m, a[1<<11], b[20];
int main()
{
while(scanf("%d%d", &n, &m) == 2)
{
memset(a, 0, sizeof(a));
for(int i=0; i<n; ++i)
{
int s = 0, x;
for(int j=0; j<m; ++j)
{
scanf("%d", &x);
if(x) s+=(1<<j);
}
a[s]++;
}
int nn = 1<<m;
for(int i=0; i<m; ++i) scanf("%d", &b[i]);
init();
S = nn + m;
T = S + 1;
for(int i=0; i<nn; ++i)
{
if(a[i])
{
add_edge(S, i, a[i]);
}
for(int j=0; j<m; ++j)
{
if(i & (1<<j)) add_edge(i, j+nn, INF);
}
}
for(int i=0; i<m; ++i) add_edge(i+nn, T, b[i]);
if(n == max_flow()) puts("YES");
else puts("NO");
}
return 0;
}
M:HDU-3081 Marriage Match II:大致题意:n个女孩,n个男孩,m对男女孩可以配对,f对女孩互相认识,如果女孩互相认识则共有男孩,男女匹配问有多少种匹配方法(女孩每次都匹配不同的男孩)。最大流 + 二分 二分最大匹配数 并查集处理朋友关系 设最大匹配数为k 建图时,s = 0,t = 2*n+1 s 向所有 女孩(1-n) 连一条容量为k的边 所有男孩向 t (n+1- 2*n) 连一条容量为k的边 女孩向能与之配对的男孩连一条容量为1的边 然后跑最大流 判断是否 满流 即 f = n*k 如果满流则代表可配对方式至少有k种,然后二分------
#include <iostream>
#include <algorithm>
#include <cmath>
#include <ctype.h>
#include <cstring>
#include <cstdio>
#include <sstream>
#include <cstdlib>
#include <iomanip>
#include <string>
#include <queue>
using namespace std;
const int inf = 0x7fffffff;
const int mn = 300, mm = 30000;
int lian[110][110];
int p[110];
void init();
int max_flow(int, int);
void addedge(int, int, int);
int par(int x)
{
if (p[x] == x)
return x;
return
p[x] = par(p[x]);
}
int main()
{
int T;
scanf("%d", &T);
int st = 0, ed = 250;
while (T--)
{
memset(lian, 0, sizeof lian);
int n, m, f;
scanf("%d %d %d", &n, &m, &f);
for (int i = 1; i <= m; i++)
{
int a, b;
scanf("%d %d", &a, &b);
lian[a][b] = 1; // a, b可匹配
}
for (int i = 1; i <= n; i++) // 初始化i的父节点
p[i] = i;
for (int i = 1; i <= f; i++) // a, b 是朋友
{
int a, b;
scanf("%d %d", &a, &b);
int x = par(a), y = par(b);
if (x != y)
p[x] = y; // 合并a, b所在集合
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (par(i) == par(j)) // i, j同源
{
for (int k = 1; k <= n; k++)
if (lian[j][k]) // j连的i也可以连
lian[i][k] = 1;
}
}
}
int ans = 0;
int l = 0, r = 100;
while (l <= r) /// 二分查找可能的ans的值
{
init(); /// 重新建图跑最大流
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if(lian[i][j])
addedge(i, 100 + j, 1);
int mid = (l + r) / 2;
for (int i = 1; i <= n; i++) // 女孩和源点相连
addedge(st, i, mid);
for (int i = 1; i <= n; i++) // 男孩和汇点相连
addedge(100 + i, ed, mid);
if (max_flow(st, ed) == mid * n) // 每个人可进行mid轮1对1匹配
{
ans = mid;
l = mid + 1;
}
else
r = mid - 1;
}
printf("%d\n", ans);
}
return 0;
}
int edge;
int fr[mn];
int lv[mn];
int cur[mn];
struct node
{
int to, val, nx, fan;
} e[mm];
void init()
{
edge = 0;
memset(fr, -1, sizeof fr);
}
void addedge(int u, int v, int w)
{
edge++;
e[edge].to = v, e[edge].val = w, e[edge].nx = fr[u], e[edge].fan = edge + 1;
fr[u] = edge;
edge++;
e[edge].to = u, e[edge].val = 0, e[edge].nx = fr[v], e[edge].fan = edge - 1;
fr[v] = edge;
}
void bfs(int s)
{
memset(lv, 0, sizeof lv);
lv[s] = 1;
queue<int>q;
q.push(s);
while (!q.empty())
{
int t = q.front();
q.pop();
for (int i = fr[t]; i != -1; i = e[i].nx)
{
if (e[i].val > 0 && !lv[e[i].to])
{
lv[e[i].to] = lv[t] + 1;
q.push(e[i].to);
}
}
}
}
int dfs(int s, int t, int f)
{
if (s == t)
return f;
for (int &i = cur[s]; i != -1; i = e[i].nx) /// 当前弧优化 否则TLE
{
if (e[i].val > 0 && lv[e[i].to] > lv[s])
{
int d = dfs(e[i].to, t, min(f, e[i].val));
if (d > 0)
{
e[i].val -= d;
e[e[i].fan].val += d;
return d;
}
}
}
return 0;
}
int max_flow(int s, int t)
{
int flow = 0;
while (1)
{
bfs(s);
if (!lv[t])
break;
for (int i = 0; i < mn; i++)
cur[i] = fr[i];
int f = -1;
while ((f = dfs(s, t, inf)) > 0)
flow += f;
}
return flow;
}
N:HDU-3416 Marriage Match IV:有n个城市m条边,告诉起点和终点,告诉你u->v的花费是w。问从起点到终点不走重复边的最短路有多少条。走过的路不能再走。输入是保存各个边的值,要是跑完最短路后,d[u]+w==d[v]就代表这条路是最短路中的,把它放进最大流的建图,流量为1,最后从s到e跑一次最大流即可。
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
#define MAXN 1000+10
#define MAXM 200000+10
#define INF 10000000
using namespace std;
struct Edge//求最大流
{
int from, to, cap, flow, next;
}edge[MAXM];
int head[MAXN], edgenum;
int cur[MAXN];
struct Dist//求最短路
{
int from, to, val, next;
}DD[MAXM];
int dead[MAXN], DDnum;
int dist[MAXN];
bool vis[MAXN];
int N, M;
int sx, ex;
void initEdge()
{
edgenum = 0;
memset(head, -1, sizeof(head));
}
void initDist()
{
DDnum = 0;
memset(dead, -1, sizeof(dead));
}
void addEdge(int u, int v, int w)
{
Edge E1 = {u, v, w, 0, head[u]};
edge[edgenum] = E1;
head[u] = edgenum++;
Edge E2 = {v, u, 0, 0, head[v]};
edge[edgenum] = E2;
head[v] = edgenum++;
}
void addDist(int u, int v, int w)
{
Dist E1 = {u, v, w, dead[u]};
DD[DDnum] = E1;
dead[u] = DDnum++;
// Dist E2 = {v, u, w, dead[v]};
// DD[DDnum] = E1;
// dead[v] = DDnum++;
}
void getDist()
{
int a, b, c;
while(M--)
{
scanf("%d%d%d", &a, &b, &c);
addDist(a, b, c);
}
scanf("%d%d", &sx, &ex);
}
void SPFA()//求最短路
{
queue<int> Q;
for(int i = 1; i <= N; i++)
{
dist[i] = i==sx ? 0 : INF;
vis[i] = i==sx ? true : false;
}
Q.push(sx);
while(!Q.empty())
{
int u = Q.front();
Q.pop();
vis[u] = false;
for(int i = dead[u]; i != -1; i = DD[i].next)
{
Dist E = DD[i];
if(dist[E.to] > dist[u] + E.val)
{
dist[E.to] = dist[u] + E.val;
if(!vis[E.to])
{
vis[E.to] = true;
Q.push(E.to);
}
}
}
}
//printf("%d\n", dist[ex]);
}
void getEdge()
{
for(int i = 1; i <= N; i++)
{
for(int j = dead[i]; j != -1; j = DD[j].next)
{
Dist E = DD[j];
if(dist[E.to] == dist[i] + E.val)
//printf("%d->%d ", i, E.to),
addEdge(i, E.to, 1);//加入最短路的边
}
}
}
bool BFS(int start, int end)//寻找增广路
{
queue<int> Q;
memset(dist, -1, sizeof(dist));
memset(vis, false, sizeof(vis));
Q.push(start);
dist[start] = 0, vis[start] = true;
while(!Q.empty())
{
int u = Q.front();
Q.pop();
for(int i = head[u]; i != -1; i = edge[i].next)
{
Edge E = edge[i];
if(!vis[E.to] && E.cap > E.flow)
{
vis[E.to] = true;
dist[E.to] = dist[u] + 1;//建立层次图
if(E.to == end) return true;//找到路径
Q.push(E.to);
}
}
}
return false;
}
int DFS(int x, int a, int end)
{
if(x == end || a == 0) return a;
int flow = 0, f;
for(int &i = cur[x]; i != -1; i = edge[i].next)
{
Edge &E = edge[i];
if(dist[E.to] == dist[x] + 1 && (f = DFS(E.to, min(E.cap - E.flow, a), end)) > 0)
{
E.flow += f;
edge[i^1].flow -= f;
flow += f;
a -= f;
if(a == 0) break;
}
}
return flow;
}
int Maxflow(int start, int end)
{
int flow = 0;
while(BFS(start, end))
{
memcpy(cur, head, sizeof(head));
flow += DFS(start, INF, end);
}
return flow;
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &N, &M);
initDist();
getDist();//建图
SPFA();//求出最短路
initEdge();
getEdge();//建新图
printf("%d\n", Maxflow(sx, ex));
}
return 0;
}