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

posted @ 2023-08-04 16:59  铃狐sama  阅读(14)  评论(0编辑  收藏  举报