网络流写的还是太少啦,可能没时间写更多的题了
主要是今天的测试网络流建模建错了,气的我写了这篇博客
可能还是因为见过的题目太少吧
最大流
这里用到的是一个残量网络的概念,我们每次找一条可以被更新的路径,减去路径上的最小容量
记得反向边增加流量,可以起到一个反悔的作用
这就是EK算法
#include<queue>
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 100007;
const int INF = 2147483647;
struct node{
int to,next,flow;
}edge[maxn*3];
int cnt=-1,head[maxn];
int last[maxn],pre[maxn],d[maxn],g[maxn];
int vis[maxn];
void add(int from,int to,int flow){
edge[++cnt].to=to;
edge[cnt].next=head[from];
edge[cnt].flow=flow;// 流量
head[from]=cnt;
}
int n,m;
bool EK_spfa(int s,int t){
for(int i=1;i<=n;i++)g[i]=INF;
memset(vis,0,sizeof(vis));
vis[s]=1;
pre[t]=-1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i!=-1;i=edge[i].next){
int to=edge[i].to;
if(edge[i].flow&&!vis[to]){
pre[to]=u;
last[to]=i;
g[to]=min(g[u],edge[i].flow);
if(!vis[to])vis[to]=1,q.push(to);
}
}
}
return pre[t]!=-1;// 能不能跑到终点
}
int ans,cost;
void MFMC(int s,int t){
while(EK_spfa(s,t)){
int now=t;
ans+=g[t];
while(now!=s){// 增广路上的流量变化
edge[last[now]].flow-=g[t];
edge[last[now]^1].flow+=g[t];// 可以反悔
now=pre[now];
}
}
}
int main(){
memset(head,-1,sizeof(head));
int s,t;
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
int x,y,z,l;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,0);// 反向0边可以反悔
}
MFMC(s,t);
cout<<ans<<endl;
}
更好一点有dinic算法,这次我们使用了dfs,本质上还是找增广路径,那是如何做到优化的呢
我们每次找的不止一条增广路径,可以多路增广,并且神奇的当前弧优化还可以减少无用的路径枚举
那么如何做到多路增广不死循环呢,我们对图按照枚举顺序进行分层,严格按照层数dfs下去,就可以了。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#define INF 2147483647
using namespace std;
const int maxn = 10010;
typedef long long ll;
struct node{
ll to;
ll next;
ll flow;
}edge[maxn*20];
ll cnt=-1;
ll head[maxn];
ll cur[maxn];
void add(ll from,ll to,ll w){
edge[++cnt].to=to;
edge[cnt].next=head[from];
head[from]=cnt;
edge[cnt].flow=w;
}
ll s,t,n,m,d[maxn];
queue<ll>q;
bool bfs(){
ll used[maxn]={0};
q.push(s);
d[s]=1;
used[s]=1;
while(!q.empty()){
ll f1=q.front();
q.pop();
for(int i=head[f1];i!=-1;i=edge[i].next){
ll to=edge[i].to;
ll f=edge[i].flow;
if(!used[to]&&f>0){
used[to]=1;
d[to]=d[f1]+1;// 分层
q.push(to);
}
}
}
return used[t];
}
ll dfs(ll x,ll a){
ll f,ret=0;
if(x==t||a==0)return a;
else{
for(ll &i=cur[x];i!=-1;i=edge[i].next){// 修改下次枚举的开头,可以直接从
//没有枚举过的边开始
node &e=edge[i];
if(d[x]+1==d[e.to]&&(f=dfs(e.to,min(a,e.flow)))>0){// 严格的层数限制
e.flow-=f;
edge[i^1].flow+=f;
ret+=f;
a-=f;
if(a==0)return ret;// 从该点增广到无法增广为止
}
}
}
return ret;// 流量没用完,返回用过的值
}
ll flow;
void Max(){
while(bfs()){
for(int i=1;i<=n;i++)// 当前弧归零
cur[i]=head[i];
flow+=dfs(s,INF);
}
cout<<flow;
}
int main(){
memset(head,-1,sizeof(head));
cin>>n>>m>>s>>t;
for(int i=1;i<=m;i++){
ll x,y,z;
scanf("%lld%lld%lld",&x,&y,&z);
add(x,y,z);
add(y,x,0);
}
Max();
return 0;
}
因为笔者过菜,所以好像印象中非模板最大流就写过一个最小割…
洛谷P1345 [USACO5.4]奶牛的电信Telecowmunication
这题要求求最小的割点数
我们思考一下增广路停止的条件,是图不连通,似乎正好满足题意
那么我们思考如何让流量的使用达到题目的要求
我们拆点,把每一个点
i
i
i变成
i
i
i和
i
+
n
i+n
i+n,中间连一条边流量为1,这样,如果走了这一条边,就说明这个点被删了,但最后残量网络不连通的时候,就达到了我们的要求
但原图中的边怎么办呢
假设现在要从原图中添加一条从x到y的边到网络中去,对于点y来说,这条边的加入不应该影响通过它的流量限制(就是前面连的那个1)发生变化,所以前面那条y到y+n的边应该接在这条边的后面,所以这条边的终点连向网络中的y,相反的,这条边应该受到x的(前面连的1)限制。因为,假设x已经被删(x到x+n满流),那么这条边再加不加都是没有变化的。所以,这条边在网络中的起点应该是x+n,这样才保证受到限制。
建边完事,就是裸的最大流板子了,dinicEK随你了
补充一个二分图匹配的写法,对于给定的二分,我们首先推荐匈牙利算法滑稽
网络流当然可以 ,我们在合法的路径之间建边,流量为无限,建一个超级源和一个超级汇
超级源连接要匹配的点,流量为1,被匹配的点连超级汇,流量也为1,直接最大流就好了
这里笔者当时遇到了一个问题,我很傻的把图建成了连线之间流量为1,超级点之间为无限,结果肯定是错的,为什么呢,因为没有达到要求每个匹配点只能提供一点流量的这个要求,那么没什么好说的了,例题是洛谷P2756 飞行员配对方案问题
费用流
裸的费用流使用spfa,求最最大增广流量的时候顺带求最短路就行了
#include<queue>
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 50007;
const int INF = 2147483647;
struct node{
int to,next,w,flow;
}edge[maxn*3];
int cnt=-1,head[maxn];
int last[maxn],pre[maxn],d[maxn],g[maxn];
int vis[maxn];
void add(int from,int to,int w,int flow){
edge[++cnt].to=to;
edge[cnt].next=head[from];
edge[cnt].flow=flow;
edge[cnt].w=w;
head[from]=cnt;
}
int n,m;
bool EK_spfa(int s,int t){
for(int i=1;i<=n;i++)d[i]=INF,g[i]=INF;
memset(vis,0,sizeof(vis));
vis[s]=1;
d[s]=0;
pre[t]=-1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=0;
for(int i=head[u];i!=-1;i=edge[i].next){
int to=edge[i].to;
if(edge[i].flow&&d[to]>d[u]+edge[i].w){
d[to]=d[u]+edge[i].w;
pre[to]=u;
last[to]=i;
g[to]=min(g[u],edge[i].flow);
if(!vis[to])vis[to]=1,q.push(to);
}
}
}
return pre[t]!=-1;
}
int ans,cost;
void MFMC(int s,int t){
while(EK_spfa(s,t)){
int now=t;
//cout<<"2";
ans+=g[t];
cost+=g[t]*d[t];
while(now!=s){
edge[last[now]].flow-=g[t];
edge[last[now]^1].flow+=g[t];
now=pre[now];
}
}
}
int main(){
memset(head,-1,sizeof(head));
int s,t;
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
int x,y,z,l;
scanf("%d%d%d%d",&x,&y,&z,&l);
add(x,y,l,z);
add(y,x,-l,0);
}
MFMC(s,t);
cout<<ans<<" "<<cost<<endl;
}
费用流的建模就比较多了,虽然我也没见过几个你妈的
各种版本的类均分纸牌
P2512 [HAOI2008]糖果传递
P4016 负载平衡问题
因为几乎就一模一样所以就讲一个
我们要求的是最小费用暗示费用流,既然都是网络流建模了,那么怎么建边达到目的呢
对于这种最终通过操作移动达到目标状态的题目,大部分都是贡献的点连接源点,需要的点连向汇点
这题也不例外,我们新建一个超级源点,一个超级汇点,对于超过平均值的点,我们连接源点,流量为超过值,费用为0,反之,小于平均值的点,我们连接汇点,流量为,差值,费用也为零
这两种边是规定最大流量的
真正转移边是两点之间,流量为无穷,费用为1,这样,达到最大流的时候,一定是所有边跑满,此时求出的最小费用就是我们要的
没代码
下面的是各种版本的传纸条
P4066 [SHOI2003]吃豆豆
P1004 方格取数
P1006 传纸条
这里就讲传纸条思路
我们把每个点拆成入和出,入向出连一条容量为1,费用为该点权值的边,这代表第一次经过
再连一条容量为INF,费用0的边,这是备用的,表示经过一次之后再经过这个点不重复选
(因为原边跑过一次后流量归零消失了)
每个点的出向右和下的入连一条容量为INF费用为0的边,达到继承的目的
超级源连(1,1)的入一条容量为2,费用0的边,表示可以出去两股
点(n,m)的出连向超级汇,容量INF,费用0;
最终的最大费用最大流就是我们要的答案
still没代码
这里先停一停吧,最近再补一点网络流的题目再继续写
2019.9.9 0:13