这里记录学习网络流时候学的知识
ISAP
下面是对于当年模仿WuY模板写的SAP的重新研究和解读
听说SAP的定义包含了dinic以及ISAP,SAP意义就是最短增广路算法
那这个应该叫做ISAP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| struct EDGE { int u,v,w; }; const int MAXV=10000,MAXE=40000; struct GRAPH { EDGE edge[MAXE]; int next[MAXE],first[MAXV],cnt,v=0; void init(int vv) { memset(first,-1,sizeof(first)); cls(edge);cls(next); cnt=-1;v=vv; } void addedge(int u,int v,int w) { edge[++cnt].u=u;edge[cnt].v=v; edge[cnt].w=w;next[cnt]=first[u]; first[u]=cnt;edge[++cnt].u=v; edge[cnt].v=u;edge[cnt].w=0; next[cnt]=first[v];first[v]=cnt; } }graph; bool visited[MAXV]; int SAP(int S,int T) { static int current[MAXE],distance[MAXV],layer[MAXV],last[MAXV],pred[MAXV]; rep(i,0,graph.v) { current[i]=graph.first[i]; distance[i]=layer[i]=0; } queue<int> q; q.push(T); visited[T]=true; layer[0]++; while (!q.empty()) { int x=q.front(); for (int k=graph.first[x];k!=-1;k=graph.next[k]) { int j=graph.edge[k].v; if (!visited[j]) { q.push(j); visited[j]=true; distance[j]=distance[x]+1; layer[distance[j]]++; } } q.pop(); } int i=S,now_flow=inf,total_flow=0; while (distance[S]<graph.v) { xxx: last[i]=now_flow; for (int k=current[i];k!=-1;k=graph.next[k]) { int j=graph.edge[k].v; if ((!graph.edge[k].w)||distance[i]!=distance[j]+1) continue; current[i]=k; int cur_flow=graph.edge[k].w; now_flow=min(cur_flow,now_flow); pred[j]=i; i=j; if (i==T) { total_flow+=now_flow; while (i!=S) { i=pred[i]; graph.edge[current[i]].w-=now_flow; graph.edge[current[i]^1].w+=now_flow; } now_flow=inf; } goto xxx; } int min_dis=graph.v-1,min_place=-1; for (int k=graph.first[i];k!=-1;k=graph.next[k]) { int j=graph.edge[k].v; if (graph.edge[k].w&&distance[j]<min_dis) { min_dis=distance[j]; min_place=k; } } current[i]=min_place; layer[distance[i]]--; if (!layer[distance[i]]) break; distance[i]=min_dis+1; layer[distance[i]]++; if (i!=S) {i=pred[i];now_flow=last[i];} } return total_flow; }
|
它不仅比较难写(其实代码长是因为我的变量名长且用的前向星而非队友们的vector),而且效率上没有什么明显优势(据网络上的说法,除非应用多路增广优化才能大大提升效率,否则和dinic还是一个数量级)。但其实有板子的话写起来没比dinic麻烦多少,就用这个吧。
dinic
dinic相对isap其实慢一些,因为每次需要重新标号时都需要把整张图bfs一遍。主要思路就是bfs求标号->找当前标号情况下的所有增广路->bfs求标号……直到找不到。
最大权闭合子图
定义
一张有向图(网上没有找到它是否一定要是dag)中,每个点都有一个权值。定义闭合子图为一个点集和点集内所有点之间的边组成的图,满足若某个点在点集内,则它的后继点也在点集内。当点集的权值和最大时,称作最大权闭合子图。
求法
该问题事实上可以转化为最小割问题。从S对每一个正权点连一条流量为权值的边,从每个负权点对T连一条流量为权值绝对值的边,将点之间的边的流量设为正无穷,则原图除去最小割即为最大权闭合子图。
证明
割边的含义
由于原图的边都是无穷大,那么割边一定是与S或T相连的。
割掉S与x的边,表示不选择x点作为子图的点
割掉x与T的边,表示选择x点为子图的点
如果S与x有边,表示x存在子图中
如果x与T有边,表示x不存在于子图中
只有S与T不连通,图才是闭合子图
若S与T联通,则必然存在一组点x,y,使得S->x->y->T。则据1的定义,x存在于子图中,y不存在于子图中,则与闭合子图定义矛盾
只要S与T不连通,图即为闭合子图(求最小割的过程中)
首先剩余的图必定为原图的子图。若某个正权点被选择了,则它指向的所有负权点必然没有被选择。对于正权点指向的别的正权点,最小割过程中将不会割去它(割去的操作只会减小流量而不会断流因此没有收益)
最小割为最优方案
最小割=(不选的正权和+要选的负权绝对值和)
最大权闭合子图=(正权和-不选的正权和-要选的负权绝对值和)=正权和-最小割
因为正权和是定值,而最小割保证值最小,所以最大权闭合子图一定最优。
参考:https://my.oschina.net/u/4417528/blog/3943851
最小路径覆盖
定义
给定有向图$G=(V,E)$。设$P$是$G$的一个简单路(顶点不相交)的集合。如果$V$中每个顶点恰好在$P$的一条路上,则称$P$是$G$的一个路径覆盖。$P$中路径可以从$V$的任何一个顶点开始,长度也是任意的,特别地,可以为$0$。$G$的最小路径覆盖是$G$的所含路径条数最少的路径覆盖。
建模
将每个点拆成两个点,形成一张二分图。设$x$点拆成$x_1$与$x_2$两个点。则若原图中存在$x$到$y$的有向边,则添加一条$x1$指向$y2$的边。原图的点数减去新图的二分图最大匹配即为答案。
原理
首先,若不存在任何边,显然答案为原图点数。同时,把拆成的两个点$x_1$当作出点,$x_2$当作入点。当新图中有一个匹配,即意味着匹配中的某个$x_1$可以连到$y_2$,则原本的$pre\rightarrow x$以及$y\rightarrow nxt$($pre$和$nxt$表示已存在的在$x$结束/从$y$开始的路径,且可以为空)两条路径显然可以变为$pre\rightarrow x\rightarrow y\rightarrow nxt$一条路径,减少了一条,答案减一。而二分图匹配的定义表明了$x_1$无法指向多个入点,$x_2$也无法指向多个出点。
二分图最小点权覆盖
定义
一张二分图,要求每条边都有至少一个端点被选择,每个点都有一个点权,问符合要求的选择的最小点权和
建模
从S向二分图一边的点连容量为点权的边,从另一边的点向T连容量为点权的边,原本的边容量设为inf
原理
感性理解:显然
证明:好像不会什么优美的,可以尝试分类讨论举例证明?
二分图最大点权独立集
定义
一张二分图,连着边的点不能都选,每个点都有一个点权,问选择的最大点权和
建模
是二分图最小点权覆盖的对偶问题,将和减去二分图最小点权覆盖即为答案
原理
设初始方案为全都选,那么就会出现很多不符合要求的。去掉哪些可以使答案最优呢?可以发现每条边都至少要去掉两个端点的其中一个,且需要去掉的点权最小。这不就是二分图最小点权覆盖问题嘛!
SPFA-dinic 最小费用最大流
将dinic的bfs换成spfa来求得最小费用的增广路。没用isap是因为它很难在处理最小费用增广路的同时更新结点的layer。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| const int MAXV=10001,MAXE=40001; struct EDGE { int u,v,w,cost; }; struct GRAPH { EDGE edge[MAXE]; int next[MAXE],first[MAXV],cnt,cur[MAXV]; inline void init() { cnt=0; cls(edge);cls(next); memset(first,-1,sizeof(first)); } inline void addedge(int u,int v,int w,int cost) { edge[cnt].u=u;edge[cnt].v=v;edge[cnt].w=w;edge[cnt].cost=cost;next[cnt]=first[u];first[u]=cnt++; edge[cnt].u=v;edge[cnt].v=u;edge[cnt].w=0;edge[cnt].cost=-cost;next[cnt]=first[v];first[v]=cnt++; } int distance[MAXV],maxflow,mincost; bool vis[MAXV]; queue <int> qu; inline bool spfa(int s,int t) { memset(distance,0x3f,sizeof(distance)); cls(vis); distance[s]=0; vis[s]=true; qu.push(s); while (!qu.empty()) { int p=qu.front(); qu.pop(); int q=first[p]; vis[p]=false; while (q!=-1) { if (edge[q].w&&distance[edge[q].v]>distance[p]+edge[q].cost) { distance[edge[q].v]=distance[p]+edge[q].cost; if (!vis[edge[q].v]) { qu.push(edge[q].v); vis[edge[q].v]=true; } } q=next[q]; } } if (distance[t]==inf) return false; return true; } int dfs(int now,const int &t,int flow) { if (now==t) {maxflow+=flow;return flow;} int sum=0; vis[now]=1; for (int q=cur[now];q!=-1;q=next[q]) { if (!vis[edge[q].v]&&edge[q].w&&distance[edge[q].v]==distance[now]+edge[q].cost) { cur[now]=q; int p=dfs(edge[q].v,t,min(flow-sum,edge[q].w)); sum+=p;edge[q].w-=p;edge[q^1].w+=p;mincost+=p*edge[q].cost; if (sum==flow) break; } } vis[now]=false; return sum; } void dinic(int s,int t) { while (spfa(s,t)) { memcpy(cur,first,sizeof(first)); dfs(s,t,inf); } } }graph;
|
最大权不相交路径
问题的定义不赘述,顾名思义。
建模
每个点拆成入点和出点,在入点和出点间连容量为1(若路径可以在点上相交则容量inf)费用为该点点权的边。每个点的出点向该点连出的边的到达点的入点连容量为1费用为0的边。S向题意中的起始点连边,题意中的终点向T连边。
最大权?若点权为正岂不是需要最大费用最大流?如何用dinic-spfa来解?
事实上 由于图是DAG,可以直接将边权取负。