• 网络流总结


    引出:

    求出一个二分图的最大匹配。

    二分图匹配怎么求?

    可以用匈牙利算法:遍历每个点,查找增广路,若找到增广路,则修改匹配集合和匹配数;否则终止算法,返回最大匹配数。这样时间复杂度是 (mathcal{O}(nm)) 的。代码实现:

    int con[N * N];
    bool vis[N * N];
    
    bool Hungary(int u)
    {
    	for (int i = head[u]; i; i = e[i].nxt)
    	{
    		int v = e[i].to;
    		if (vis[v]) continue;
    		vis[v] = 1;
    		if (!con[v] || Hungary(con[v]))
    		{
    			con[v] = u;
    			return 1;
    		}
    	}
    	return 0;
    }
    

    二分图有很多优秀的性质:

    最大匹配数=最小点覆盖数,最大独立集与最大匹配数互补,DAG最小路径覆盖=DAG原图节点数-对应二分图最大匹配。

    也就是说用简简单单小 DFS 就能快速地求出这么多问题!但是这不够优秀,我们用网络最大流解决二分图一类的问题可以达到 (mathcal{O}(msqrt{n}))。所以在这里介绍一下网络流的解法之类。

    正文:

    当然网络流不仅局限于二分图的问题,它还比匈牙利更加灵活。

    在开始之前,先了解关于网络流的一些基本概念吧。

    基本概念:

    网络流图:

    1. 有且仅有一个入度为 (0) 的源点 (S)
    2. 有且仅有一个出度为 (0) 的汇点 (T)
    3. 每条有向边都有流量上限。

    可行流:

    1. 每个点((S,T) 除外)满足流入等于流出,每条边满足流入等于流出且不超过流量上限。
      1.从源点出发的流全经过网络到汇点。

    饱和弧:

    饱和弧是指在一个可行流中,一条通过流量等于流量上限的边。若是小于,这条边就叫非饱和弧。

    零弧:

    零弧是指在一个可行流中,一条通过流量为零的边。

    前向弧和后向弧:

    将网络流图看作是无向图的情况下,前向弧是指一条方向是和源点流向汇点方向一致的边,后向弧则反之。

    后向边:

    和有向图的有向边方向相反的边。

    增广路径:

    一段路径满足:

    1. 所有前向弧都是非饱和弧。
    2. 所有后向弧都是非零弧。

    残量网络:

    对于某一条边,还能再有多少流量经过。

    最大流:

    每次用 BFS 找一条最短的增广路径,然后沿着这条路径修改流量值(实际修改的是残量网络的边权)。当没有增广路时,算法停止,此时的流就是最大流。

    然后这个过程可以用 Dinic 优化:

    1. 分层图优化:Dinic 算法首先对图进行一次 BFS,然后在其生成的分层图中多次 DFS。这样就切断了原有图中的许多不必要连接。这样我们可以进行多路增广,减少了调用增广函数的次数。
    2. 当前弧优化:每次 DFS 完,找到路径中容量最小的一条。在这条边之前的路径的容量大于等于这条边的容量。那么从这条边之前的点,可能引发出别的增广路径。
    3. -1 优化:在同一次 DFS 中,如果从一个点引发不出任何增广路径,就让它在层次图里抹去。

    代码:

    int n, m, s, t, tot;
    struct edge
    {
    	int y, w, op, next;
    } e[M];
    int head[N];
    
    void Add(int x, int y, int w)
    {
    	e[++tot] = (edge){y, w, tot + 1, head[x]}; 
    	head[x] = tot;
    	e[++tot] = (edge){x, 0, tot - 1, head[y]}; 
    	head[y] = tot; 
    }
    
    int dis[N];
    queue <int> que;
    
    bool bfs()
    {
    	while(!que.empty())que.pop();
    	memset(dis, 60, sizeof(dis));
    	dis[s] = 0;
    	que.push(s);
    	while(!que.empty())
    	{
    		int x = que.front();que.pop();
    		for (int i = head[x]; i; i = e[i].next)
    		{
    			int y = e[i].y;
    			if(dis[y] >= dis[x] + 1 && e[i].w)
    			{
    				dis[y] = dis[x] + 1;
    				if(y == t) return 1;
    				que.push(y);
    			}
    		}
    	}
    	return 0;
    }
    
    ll dfs(int x, ll f)
    {
    	if(x == t) return f;
    	ll sum = 0;
    	for (int i = head[x]; i; i = e[i].next)
    	{
    		int y = e[i].y;
    		if(dis[y] == dis[x] + 1 && e[i].w)
    		{
    			ll f2 = dfs(y, min(e[i].w * 1ll, f - sum));
    			if (!f2) dis[y] = -1;         //-1优化
    			e[i].w -= f2; 
    			e[e[i].op].w += f2;
    			sum += f2;
    			if (sum == f) break;
    		}
    	}
    	return sum;
    }
    
    ll dinic()
    {
    	ll sum = 0;
    	while(bfs()){sum += dfs(s, 1010580540);}
    	return sum;
    }
    

    最小割:

    首先,割是一组边,如果去掉割边,(S,T) 就不会联通,那么最小割就是这组边的最小价值。

    其次,最小割等于最大流,不会证明。

    费用流:

    BFS 换成 SPFA。

    
    int n, m, s, t, tot;
    struct edge
    {
    	int y, w, z, op, next;
    } e[M];
    int head[N];
    
    void Add(int x, int y, int w, int z)
    {
    	e[++tot] = (edge){y, w, z, tot + 1, head[x]}; 
    	head[x] = tot;
    	e[++tot] = (edge){x, 0, -z, tot - 1, head[y]}; 
    	head[y] = tot; 
    }
    
    int dis[N], incf[N], pre[N];
    bool vis[N];
    queue <int> que;
    
    bool spfa()
    {
    	while(!que.empty())que.pop();
    	memset(dis, 60, sizeof(dis));
    	memset(vis, 0, sizeof(vis));
    	dis[s] = 0;
    	que.push(s);
    	vis[s] = 1;
    	incf[s] = 1 << 30;
    	while(!que.empty())
    	{
    		int x = que.front();que.pop();vis[x] = 0;
    		for (int i = head[x]; i; i = e[i].next)
    		{
    			int y = e[i].y;
    			if(dis[y] > dis[x] + e[i].z && e[i].w)
    			{
    				dis[y] = dis[x] + e[i].z;
    				incf[y] = min(incf[x], e[i].w);
    				pre[y] = i;
    				if(!vis[y]) vis[y] = 1, que.push(y);
    			}
    		}
    	}
    	if (dis[t] == 1010580540)
    		return 0;
    	else
    		return 1;
    }
    
    int maxflow, mincost;
    
    void MCMF()
    {
    	while(spfa())
    	{
    		int x = t;
    		maxflow += incf[t];
    		mincost += dis[t] * incf[t];
    		int i;
    		while(x != s)
    		{
    			i = pre[x];
    			e[i].w -= incf[t];
    			e[e[i].op].w += incf[t];
    			x = e[e[i].op].y;
    		}
    	}
    }
    
    
  • 相关阅读:
    《哈佛大学公开课:公正该如何做是好?》学习笔记3
    iPhone客户端开发笔记(三)
    iPhone客户端开发笔记(一)
    今天讨论了下本地化服务信息应用
    云游第一天感受
    昨晚调试一段PHP程序时遇到的三个问题
    iPhone客户端开发笔记(四)
    《哈佛大学公开课:公正该如何做是好?》学习笔记2
    Oracle10g数据库归档与非归档模式下的备份与恢复
    javascript 实现页面间传值
  • 原文地址:https://www.cnblogs.com/GJY-JURUO/p/12109923.html
Copyright © 2020-2023  润新知