网络流
网络流
---by 蒟蒻鱼
解决什么问题:
有一自来水运送系统(可理解为有向图且有边权),起始点S,目标点为T,途中的每一个管道都有一个最大的容量(即权值)
- 求S到T的最大水流量为多少
这样的问题就是网络流类问题,而此题就是要求最大流
如何求
Dinic算法 求最大流
(有些不理解,但请记住一些结论)
粗略的步骤:
1.找增广路,将路上的最小边权加入答案
2.进行一系列操作
重复上述操作直到找不出增广路就返回
详细的解释
2.的解释
- 反悔机制
在每一条边加一条反向边维护这条边使其与其正边的值相加恒为原边权
什么意思?
简单地来讲就是原边权值加上某个值,反向边就减去这个值
- 找增广路
在找到增广路时记得将路上的所有边权都减去增广路上的最小边的边权(tip 记得将每个边的反向边加上对应的值 即增广路上的最小边的边权)
好啦,开始打代码 个鬼
还没有说怎样存图呢
一种对于我十分新鲜的存图方法
链式向前星
利用链表存边,与邻接表存图只有这唯一一个区别但提速很大
具体声明与存储方式
int head[maxn]; //head[u]记录u点的头指针
struct edge
{
int to; //指向的下一个点的编号
int cost; //边权
int next; //指向的同一起始点的下一条边的编号 方便广搜遍历
} es[maxm*2]; //记录边以及它的反弧
void addedge(int u,int v,int cost) //添加边以及其反弧
{
es[cnt].to=v;
es[cnt].cost=cost;
es[cnt].next=head[u];//添加边
/*
1.将新边的头指针指向原来U点的头指针
2.将原来U点的头指针指向新边
3.并将新边的边权赋为cost
这三步操作即将此边放在链表的首位
*/
head[u]=cnt;
++cnt;
es[cnt].to=u;
es[cnt].cost=0;
es[cnt].next=head[v]; //添加反弧
/*
进行与正边相反的操作
并将反弧的初始权值赋为0
*/
head[v]=cnt;
++cnt;
}
好啦,说完上述分析以及存点方法
终于开始打模板题代码
#include<bits/stdc++.h>
using namespace std;
int INF=1e9;
const int maxn=100007;
const int maxm=100007;
int l[maxn]; //记录当前点在增广路中的深度为深搜提供便利
int head[maxn]; //head[u]记录u点的头指针
struct edge
{
int to; //指向的下一个点的编号
int cost; //边权
int next; //指向的同一起始点的下一条边的编号 方便广搜遍历
} es[maxm*2]; //记录边以及它的反弧
int cnt=0; //记录边的个数即其编号
void init(){
for(int i=1;i<=maxn*2;i++) head[i]=-1;
}
void addedge(int u,int v,int cost) //添加边以及其反弧
{
es[cnt].to=v;
es[cnt].cost=cost;
es[cnt].next=head[u];//添加边
/*
1.将新边的头指针指向原来U点的头指针
2.将原来U点的头指针指向新边
3.并将新边的边权赋为cost
这三步操作即将此边放在链表的首位
*/
head[u]=cnt;
++cnt;
es[cnt].to=u;
es[cnt].cost=0;
es[cnt].next=head[v]; //添加反弧
/*
进行与正边相反的操作
并将反弧的初始权值赋为0
*/
head[v]=cnt;
++cnt;
}
bool ap(int s,int t) //搜索是否能找到增广路
{
memset(l,0,sizeof(l));
l[s]=1;
queue<int> q; //利用队列存点更加便捷
q.push(s);
while(!q.empty()) //广搜过程不需解释
{
int u=q.front();
q.pop();
if(u==t) //如果搜索到了汇点
return true;
for(int v=head[u];v!=-1;v=es[v].next){
int k=es[v].to; //记录当前遍历到的下一个点
if(!l[k]&&es[v].cost){ //如果此点未被遍历且连接两个点的两条边权不为零
l[k]=l[u]+1;
q.push(k);
}
}
}
return false; //没有找到
}
int dfs(int x,int t,int ans) //x为当前遍历到的点,t为汇点,ans为增广路的增益也是流量的限制
{
if(x==t){
return ans;
}
int sum=0;
for(int i=head[x];i!=-1;i=es[i].next){
if(es[i].cost&&l[x]==l[es[i].to]-1){ //如果改边流量没有用完且下一个点在增广路上的下一个点
int f=dfs(es[i].to,t,min(es[i].cost,ans-sum));
es[i].cost-=f;
es[i^1].cost+=f; //反弧加上相应值
sum+=f;
if(sum==ans)
return sum;
}
}
return sum;
}
int dinic(int s,int t) //s为源点,t为汇点
{
int ans=0;
while(ap(s,t)) //重复找s到t的增广路
{
ans+=dfs(s,t,INF);
}
return ans;
}
int N,M,S,T;
int main()
{
init();
cin>>N>>M>>S>>T;
for(int i=1;i<=M;i++){
int aa,bb,cc;
cin>>aa>>bb>>cc;
addedge(aa,bb,cc);
}
int ans=dinic(S,T);
cout<<ans;
return 0;
}
/*
输入样例
3 2 1 3
1 2 1
2 3 1
输出
1
*/
为方便打板特别附上无注解的代码
#include<bits/stdc++.h>
using namespace std;
int INF=1e9;
const int maxn=100007;
const int maxm=100007;
int l[maxn];
int head[maxn];
struct edge
{
int to;
int cost;
int next;
} es[maxm*2];
int cnt=0;
void init(){
for(int i=1;i<=maxn*2;i++) head[i]=-1;
}
void addedge(int u,int v,int cost)
{
es[cnt].to=v;
es[cnt].cost=cost;
es[cnt].next=head[u];
head[u]=cnt;
++cnt;
es[cnt].to=u;
es[cnt].cost=0;
es[cnt].next=head[v];
head[v]=cnt;
++cnt;
}
bool ap(int s,int t)
{
memset(l,0,sizeof(l));
l[s]=1;
queue<int> q;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
if(u==t)
return true;
for(int v=head[u];v!=-1;v=es[v].next){
int k=es[v].to;
if(!l[k]&&es[v].cost){
l[k]=l[u]+1;
q.push(k);
}
}
}
return false;
}
int dfs(int x,int t,int ans)
{
if(x==t){
return ans;
}
int sum=0;
for(int i=head[x];i!=-1;i=es[i].next){
if(es[i].cost&&l[x]==l[es[i].to]-1){
int f=dfs(es[i].to,t,min(es[i].cost,ans-sum));
es[i].cost-=f;
es[i^1].cost+=f;
sum+=f;
if(sum==ans)
return sum;
}
}
return sum;
}
int dinic(int s,int t)
{
int ans=0;
while(ap(s,t))
{
ans+=dfs(s,t,INF);
}
return ans;
}
int N,M,S,T;
int main()
{
init();
cin>>N>>M>>S>>T;
for(int i=1;i<=M;i++){
int aa,bb,cc;
cin>>aa>>bb>>cc;
addedge(aa,bb,cc);
}
int ans=dinic(S,T);
cout<<ans;
return 0;
}
好了模板就是这样
看一下有什么练习