网络流学习笔记
最大流
dinic算法:每次增广出长度一样的若干条路径,用朴素的bfs和dfs实现。
费用流
EK算法:每次找出一条费用最小的流量跑走,这样可以保证最后一定是最大流且此时费用最小。
zkw费用流:不会,留坑代填。
最小割:给定一张图,问使源点和汇点不连通的最小代价,可以证明这是最大流。
最小割树:一种神奇的树,构建方法如下,1、从当前图中随便找出两个点,跑一边最小割求出此时两个点之间的最小割,在这两个点间连边,然后我们把满流的边删掉,这样会把图分成好几个块,我们令s所在块为一块,剩下的点为一块,在分治下去,这样我们一共要跑n-1次最小割,连出来的一定是一颗树。
有一个结论,任意两点之间的最小割是最小割树上两点路径上边权最小值。为什么,考虑我们把图分为两块的时候,当前的最小割就是我们把图分为两块的最小割,也就是从两块中任意拿出两个点,它们之间的最小割的上界。
二元关系最小割
有这样的一个问题,有多组二元关系,形如xy都选的价值,选一个的价值,都不选的价值,可以建出这么一张图。
然后我们可以根据所得的信息求出每条边的流量,跑最小割就可以了。
最大权闭合子图
有这样一个问题,给定一个DAG,求一个最大权子图,满足这个子图里的所有元素连出去的边指向的点也都在这个子图里。
我们可以贪心的先把所有正权点都选上 ,但这样可能不满足它是闭合的,
所以我们从源点向每个正权点连流量为点权的边,所有负权点向汇点连流量为点权的绝对值的边。
相当于是说,对于一些不合法的正负点权来说,要么就花正点权的代价不要正点,要么就把负点权选上。
这张图的最大流,也就是最小割,就是我们的最小代价。
bzoj4873寿司餐厅:mx^2+cx表示选择一种代号就会产生固定的mx^2代价,每选择一种寿司就会增加x的代价,考虑如下限制,区间lr被选择时,区间[l+1,r]区间[l,r-1]也必须被选择,这样的话闭合子图的模型就出来了,对于每种代号新开一个点,所有长度为1的区间向所对应的代号连边,表示选了这个区间,这种代号产生的代价就必须产生,我们发现答案就是这张图上的最大权闭合子图。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define N 12009 #define xi 102 #define M 1000002 #define inf 2e9 using namespace std; typedef long long ll; queue<int>q; int head[N],cur[N],deep[N],tot=1,top,n,m,a[N],b[N],val[N],id[xi][xi],c[N],num; ll ans=0; inline int rd(){ int x=0;char c=getchar();bool f=0; while(!isdigit(c)){if(c=='-')f=1;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return f?-x:x; } struct edge{ int n,to,l; }e[M]; inline void add(int u,int v,int l){ e[++tot].n=head[u];e[tot].to=v;head[u]=tot;e[tot].l=l; e[++tot].n=head[v];e[tot].to=u;head[v]=tot;e[tot].l=0; } inline bool bfs(int s,int t){ memset(deep,0,sizeof(deep)); memcpy(cur,head,sizeof(head)); q.push(s);deep[s]=1; while(!q.empty()){ int u=q.front();q.pop(); for(int i=head[u];i;i=e[i].n){ int v=e[i].to; if(!deep[v]&&e[i].l){ deep[v]=deep[u]+1; q.push(v); } } } return deep[t]; } int dfs(int u,int t,int l){ if(!l||u==t)return l; int flow=0,f; for(int &i=cur[u];i;i=e[i].n){ //!!! int v=e[i].to; if(deep[v]==deep[u]+1&&(f=dfs(v,t,min(l,e[i].l)))){ e[i].l-=f;e[i^1].l+=f;l-=f;flow+=f; if(!l)break; } } return flow; } int main(){ n=rd();m=rd(); for(int i=1;i<=n;++i)a[i]=rd(),b[i]=a[i]; sort(b+1,b+n+1); top=unique(b+1,b+n+1)-b-1; for(int i=1;i<=n;++i)c[i]=lower_bound(b+1,b+top+1,a[i])-b; for(int i=1;i<=n;++i) for(int j=i;j<=n;++j){ id[i][j]=++num;val[num]=rd(); if(id[i-1][j])add(id[i-1][j],id[i][j],inf); if(id[i][j-1])add(id[i][j],id[i][j-1],inf); } int T=num+top; for(int i=1;i<=n;++i)val[id[i][i]]-=a[i],add(id[i][i],num+c[i],inf),val[num+c[i]]=-m*a[i]*a[i]; for(int i=1;i<=T;++i){ if(val[i]>0)add(0,i,val[i]),ans+=val[i]; else add(i,T+1,-val[i]); } while(bfs(0,T+1))ans-=dfs(0,T+1,inf); printf("%lld\n",ans); return 0; }
有上下界的网络流
有上下界的网络流可以用来解决每条边的流量是一个区间的问题。
无源汇有上下界的可行流(环流):我们可以先让每条边都跑上流量的下界,但这样流量有可能不守恒,所以我们新建源点和汇点,令每个点当前流入量和流出量差的绝对值为x。
如果流入量大于流出量,我们就从源点向该点连流量为x的边,相当于让这个点流出x的流量,反之我们从该点向汇点连流量为x的边,相当于让这个点再流进x的流量。
那么我们新加入的每条边都导入/导出了我们一开始直接跑掉流量下界时造成的流入量和流出量的差。
跑完最大流之后检查一下后加入的边是否满流,如果满流,说明每条边都满足了要求。
有源汇有上下界的可行流:这个和上面的差别就是有了源汇,其实我们可以通过转换变成上面的问题,判断流了多少流量,只需要判断t-s边的流量就好了。
有源汇有上下界的最大流:先跑可行流,此时下界已经满足合法,我们可以把超级源和超级汇去掉,在残量网络上跑最大流就可以了,最终的最大流流量=可行流流量+新增广出的流量,其实这个可行流量在我们第二次最大流的时候就自动加上了,其实就是反边的流量。
有源汇有上下界的最小流:这个比较难理解,我们先不加t-s的边,然后跑一个可行流,再把t-s边加上,再跑一个可行流,那么t-s的流量就是最小流,因为我们要满足流量下限的限制,所以这一步可以尽量填充较多的流量,减小第二次产生的流量。
bzoj4464[Jsoi2013]旅行时的困惑:裸的树上最小路径覆盖模型,可以用费用流做,甚至可以用树形dp做,用最小流的话图上边设为(1,inf),s到i设为(0,inf),i到t设为(0,inf),跑有源汇最小流。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #define N 101002 #define inf 2e9 using namespace std; queue<int>q; int head[N],tot=1,deep[N],cur[N],n,du[N],ans; inline int rd(){ int x=0;char c=getchar();bool f=0; while(!isdigit(c)){if(c=='-')f=1;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return f?-x:x; } struct edge{int n,to,l,f;}e[N<<4]; inline void add(int u,int v,int l){ e[++tot].n=head[u];e[tot].to=v;head[u]=tot;e[tot].l=l; e[++tot].n=head[v];e[tot].to=u;head[v]=tot;e[tot].l=0; } inline bool bfs(int s,int t){ memset(deep,0,sizeof(deep)); memcpy(cur,head,sizeof(cur)); q.push(s);deep[s]=1; while(!q.empty()){ int u=q.front();q.pop(); for(int i=head[u];i;i=e[i].n){ int v=e[i].to; if(!deep[v]&&e[i].l){deep[v]=deep[u]+1;q.push(v);} } } return deep[t]; } int dfs(int u,int t,int l){ if(!l||u==t)return l; int flow=0,f; for(int &i=cur[u];i;i=e[i].n){ int v=e[i].to; if(deep[v]==deep[u]+1&&(f=dfs(v,t,min(l,e[i].l)))){ e[i].l-=f;e[i^1].l+=f;flow+=f;l-=f; if(!l)break; } } return flow; } inline int dinic(int s,int t){ int ans=0; while(bfs(s,t))while(int f=dfs(s,t,inf))ans+=f; return ans; } int main(){ n=rd();int x,y; for(int i=1;i<n;++i){ x=rd();y=rd();x++;y++; add(y,x,inf); du[x]++;du[y]--; } for(int i=1;i<=n;++i)add(0,i,inf),add(i,n+1,inf); for(int i=1;i<=n;++i) if(du[i]<0)add(i,n+3,-du[i]);else add(n+2,i,du[i]); dinic(n+2,n+3); add(n+1,0,inf); cout<<dinic(n+2,n+3); return 0; }
未完待续。。。