上周末打了两场网络赛,对于网络流的应用有了更深的体会【简单来讲就是还不懂得怎么自己写,就只是好像懂了一丢丢要怎么用】。把kuangbin大佬的模板再手敲一遍加上自己的注释。
//最小费用最大流
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<set>
#include<map>
using namespace std;
const int maxn = 1e4;
const int maxm = 1e5;
const int inf = 0x3f3f3f3f;
struct Edge
{
int to,next,cap,flow,cost;
}edge[maxm];
int head[maxn],tol;
int pre[maxn],dis[maxn];
bool vis[maxn];
int N; //节点个数,编号0->N-1 !全局变量 需要init赋值或主函数改变
void init(int n)
{
N=n;
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<N;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;
}
int MCMF(int s,int t,int &cost) //MinCostMaxFlow 返回最大流,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;
}
最小费用最大流属于网络流的一种基本算法,上周末的应用中,一道是滑雪求最长路径(滑雪只能从高到低,所以是有向路),一道是在n个城市中辗转买、卖书的问题(中间没有多次买卖,choose two cities to buy and sell)。
两道题的共同特点是n个点相互之间有路,不管是不是有向的,然后我们要找到最符合要求的一种方式。两道题中我们都可以额外添加两个点作为入点和汇点,用流量来限制通过每条路、每个点的次数,用费用来体现两点之间的【贡献】。
在滑雪的问题中,因为题目中要求每个点只能经过一次(流量限制的是每条路经过的次数),所以我们可以把每个点拆成两个点所连通的路,并且把这条路的流量设置为1。
两道题中,所求的其实是最大费用最大流的问题(滑雪路径最长,赚的钱最多)。我们可以将每条路的花费设置成负数,就可以了。有向边和无向边的区别我们可以建两个有向边。
// EK 最大流
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int MAXN=500;
const int INF=0x3fffffff;
int g[MAXN][MAXN];//存边的容量,没有边的初始化为0
int path[MAXN], flow[MAXN], S, T;
int n;//点的个数,编号0-n.n包括了源点和汇点。
queue<int>q;
int bfs()
{
int i,t;
while(!q.empty())q.pop();//把清空队列
memset(path,-1,sizeof(path));//每次搜索前都把路径初始化成-1
path[S]=0;
flow[S]=INF;//源点可以有无穷的流流进
q.push(S);
while(!q.empty())
{
t=q.front();
q.pop();
if(t==T)break;
//枚举所有的点,如果点的编号起始点有变化可以改这里
for(i=0;i<=n;i++)
{
if(i!=S&&path[i]==-1&&g[t][i])
{
flow[i]=flow[t]<g[t][i]?flow[t]:g[t][i];
q.push(i);
path[i]=t;
}
}
}
if(path[T]==-1)return -1;
return flow[T];
}
int Edmonds_Karp()
{
int max_flow=0;
int step, now, pre;
while((step=bfs()) != -1)
{
max_flow += step;
now = T;
while(now != S)
{
pre = path[now];
g[pre][now] -= step;
g[now][pre] += step;
now = pre;
}
}
return max_flow;
}
int main()
{
// g[u][v] = w;
return 0;
}
EndEnd