vector最大流试预习
最大流预习
前情提要:
看看人家初中,早就学完最大流最小割,还在最小费用流了,我却从来没有正式接触过
太丢脸了吧
所以今天尝试来写一下EK和DI
EK算法流程
1.初始化
2.bfs找到一条增广路
3.找到限制边残余量k,这条路上正向边都减去k,反向边残余流量都加上k。
4.重复步骤2直到不再有增广路存在
然后自己试着打了一下EK,woc竟然过了(想当年,我di写了好几万年......)
应该只有我会写这种vector的代码吧
重要代码实现:
1.vector怎么快速找反向边呢?
假设我现在已知u->v这条边我怎么快速找到v->u这条边呢?
很明显,我反向边建立的时候正是正向边建立的时候
所以,我可以记录一下对方vector存储的位置-->未存储前的size
为什么是未存前?这是因为vector是从0开始标号的,如果从1开始,我加入的这个位置肯定是size+1。
2.已知u,v,两者我都不知道具体存储位置怎么办?
这个也很简单,我利用了cb这个数组,cb(i,j)表示我第一次建立i->j这条边的时候,j存在vector(i)的哪个位置
那么很显然,这个值就是vector(i)的size
那么我利用mp(i,cb(i,j))就能找到i->j这条边具体位置
3.去重怎么办?
很明显的是同一条边具有可加性,然后判断下cb我是否已经赋值了,如果赋值了,就找到这条边然后加就行了
4.最后一定记住bfs及其小细节即可!
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
struct node{
int to;
int c;//最大流量
int f;//残余流量,最开始就是c
int rev;//反向边在另一个地方的编号 ,即已知边u->v:在mp[v][rev]就正好是u处,正好就是反向边
};
int n,m,s,t;
vector<node>mp[205];
vector<node>rev[205];
bool vis[205];
int dis[205];
int pre[205];
int cb[205][205];//重边可加性 ,值则是表示存储位置
int ans=0;
bool bfs(){
memset(vis,0,sizeof(vis));
queue<int>q;
vis[s]=1;
dis[s]=2005020600;//从原点到达x点的最大流
q.push(s);
while(q.size()){
int tmp=q.front();
q.pop();
for(int i=0;i<mp[tmp].size();i++){
if(mp[tmp][i].f==0){
continue;
}
int to=mp[tmp][i].to;
if(vis[to]){
continue;
}
dis[to]=min(dis[tmp],mp[tmp][i].f);//这条路我只能流他剩下的
pre[to]=tmp;
q.push(to);
vis[to]=1;
if(to==t){
return 1;
}
}
}
return 0;
}
inline void updata() { //更新所经过边的正向边权以及反向边权
int x=t;
while(x!=s) {
int to=pre[x];
int id1=cb[x][to];
mp[x][id1].f+=dis[t];
int id=mp[x][id1].rev;
mp[to][id].f-=dis[t];
x=to;
}
ans+=dis[t]; //累加每一条增广路经的最小流量值
}
void EK(){
while(bfs()!=0){
updata();
}
}
signed main(){
ios::sync_with_stdio(false);
cin >> n >> m >> s >> t;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cb[i][j]=-1;
}
}
for(int i=1;i<=m;i++){
int u,v,w;
cin >> u >> v >> w;
if(cb[u][v]==-1){
int revu=mp[u].size();
int revv=mp[v].size();
cb[u][v]=revu;
cb[v][u]=revv;
mp[u].push_back((node){v,w,w,revv});
mp[v].push_back((node){u,w,0,revu});
}
else{
int id=cb[u][v];
mp[u][id].c+=w;
mp[u][id].f+=w;
int id2=mp[u][id].rev;
mp[v][id2].c+=w;
}
}
EK();
cout<<ans;
}
dinic算法及其流程
1.利用bfs寻找分层图,找不到的时候结束算法
2.找到分层图后,dfs对所有满足“可流”及“层次相邻且向深处走”的点进行流动
3.dfs回来时,改变正图和反图,统计这一次的答案,然后重复回第一步
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,s,t;
#define int long long
struct node{
int to;
int left;
int mx;
int rev;
};
vector<node>mp[205];
int cf[205][205];
int dep[205];
bool bfs(){
memset(dep,-1,sizeof(dep));
queue<int>q;
dep[s]=1;
q.push(s);
while(q.size()){
int u=q.front();
q.pop();
for(int i=0;i<mp[u].size();i++){
int to=mp[u][i].to;
if(dep[to]==-1&&mp[u][i].left>0){
q.push(to);
dep[to]=dep[u]+1;
}
}
}
if(dep[t]!=-1){
return 1;
}
else{
return 0;
}
}
int dfs(int x,int val){
if(x==t||!val){
return val;
}
int tmp=0;
for(int i=0;i<mp[x].size();i++){
int to=mp[x][i].to;
if(mp[x][i].left>0&&dep[to]==dep[x]+1){
int flow=dfs(to,min(val,mp[x][i].left));
val-=flow;
int posb=mp[x][i].rev;
mp[to][posb].left+=flow;
mp[x][i].left-=flow;
tmp+=flow;
if(!val){
break;
}
}
}
if(!tmp){
dep[x]=-1;
}
return tmp;
}
int dinic(){
int ret=0;
while(bfs()){
ret+=dfs(s,0x3f3f3f3f);
}
return ret;
}
signed main(){
ios::sync_with_stdio(false);
cin >> n >> m >> s >> t;
memset(cf,-1,sizeof(cf));
for(int i=1;i<=m;i++){
int a,b,w;
cin >> a >> b >> w;
if(cf[a][b]==-1&&cf[b][a]==-1){
int asize=mp[a].size();
int bsize=mp[b].size();
mp[a].push_back((node){b,w,w,bsize});
mp[b].push_back((node){a,0,w,asize});
cf[a][b]=asize;
cf[b][a]=bsize;
}
else{
int posb=cf[a][b];
mp[a][posb].mx+=w;
mp[a][posb].left+=w;
int posa=mp[a][posb].rev;
mp[b][posa].mx+=w;
}
}
cout<<dinic();
}
小细节都是同上的
最大流的应用问题
1.最基本的应用就是他很明显的给出了边,并且要你求最大值
例如:草地排水
https://www.luogu.com.cn/problem/P2740
像这种直接dinic解决就好了
2.有点难度的是二分图最大匹配和最大流的互换
例如:小行星
http://222.180.160.110:1024/problem/5746
首先可以看出这是二分图最小覆盖
最小覆盖=最大匹配
最大匹配转最大流方法:
1.虚拟超级源点,连接二分图中一部分的所有点,容量为1
2.虚拟超级汇点,被二分图中另外一部分所有点链接,容量为1
3.第一部分和第二部分的无相边变为有向(全部从部分1连向部分2)
然鹅相比匈牙利算法,时间复杂度较高了吗(边增多了,还是n方m的算法)
并不是,时间还要低,dinic大概在根号n*m左右
原因在于:我的流的限制都是1