网络流小结

注:因为风骨傲天习惯用(Dfs+Dinic),所以不会用到(EK)等其他形式,而预流推进等较高级的,等我学了再说吧……

一、基础知识

事实上,这一部分只会包括最大流和最小费用最大流的略解,后面会补上带上下界的。

  • 最大流:(Bfs+Dfs)不多说,反正不难。
  • 最小费用最大流:最短路(+Dfs),和上面差不多,只是改一下(Bfs)即可。
  • 当前弧优化:用(cur)记一下上一次跑哪了即可(上代码,东西都在代码里)。

最大流:

inline bool bfs()
{
	memset(d,0,sizeof(d));d[s]=1;
	queue<int> q;q.push(s);
	while(!q.empty())
	{
		int x=q.front();q.pop();
		for(int i=head[x];i!=-1;i=edge[i].next)
			if(!d[edge[i].to]&&edge[i].dis)
				d[edge[i].to]=d[x]+1,q.push(edge[i].to);
	}
	return d[t]?1:0;
}
inline int dfs(int pos,int dis)
{
	if(pos==t) return dis;
	for(int i=cur[pos];i!=-1;i=edge[i].next)
	{
		cur[pos]=i;
		if(d[edge[i].to]==d[pos]+1&&edge[i].dis>0)
		{
			int data=dfs(edge[i].to,min(edge[i].dis,dis));
			if(data>0)
			{
				edge[i].dis-=data;
				edge[i^1].dis+=data;
				return data;
			}
		}
	}
	return 0;
}
inline int Dinic()
{
	int ans=0;
	while(bfs())
	{
		memcpy(cur,head,sizeof(cur));
		while(int data=dfs(s,0x3f3f3f3f))
			ans+=data;
	}
	return ans;
}

费用流:

inline bool bfs()
{
    for(int i=1;i<=n;i++) d[i]=-0x3f3f3f3f;
    memset(v,0,sizeof(v));memset(flo,0,sizeof(flo));
    queue<int> q;q.push(s);d[s]=0;v[s]=1;flo[s]=1;
    while(!q.empty())
    {
        int x=q.front(); q.pop(); v[x]=0;
        for(int i=head[x];i!=-1;i=edge[i].next)
        {
            int y=edge[i].to;
            if(edge[i].dis>0&&d[y]<d[x]+edge[i].cos)
            {
                d[y]=d[x]+edge[i].cos;
                flo[y]=flo[x]+1;
                if(!v[y]){q.push(y);v[y]=1;}
            }
        }
    }
    if(d[t]==-0x3f3f3f3f) return 0;
    else return 1;
}
int dfs(int pos,int dis)
{
    if(pos==t) return dis;
    for(int i=cur[pos];i!=-1;i=edge[i].next)
        if(flo[edge[i].to]==flo[pos]+1&&edge[i].dis!=0&&d[edge[i].to]==d[pos]+edge[i].cos)
        {
      		int data=dfs(edge[i].to,min(dis,edge[i].dis));
      		if(data>0)
      		{
      			edge[i].dis-=data;
      			edge[i^1].dis+=data;
      			ans_cos+=edge[i].cos*data;
      			cur[pos]=i;
            	return data; 
        	}
      	}
    return 0;
}
int dinic()
{
    int ans=0;
    while(bfs())
    {
        memcpy(cur,head,sizeof(head));
        while(int data=dfs(s,0x3f3f3f3f))
            ans+=data;
    }
    return ans;
}

二、最小割

实际上,最小割的应用很多:详见国家集训队胡伯涛论文:最小割模型在信息学中的应用

1.最大权闭合子图

​ 闭合图:对于一个有向图(G),存在点集合(V),任取点(u)属于(V)(u)的出边的另一个点也属于(V),则为闭合图。

​ 最大闭合子图,即原图每个点有点权,求原图的闭合子图中最大点权和(点权可正可负)。

​ 转化:(S)向所有正权点建边,容量为点权,所有负权点向(T)建边,容量为点权绝对值,原边容量为(Inf)

​ 最大权(=)正权和(-)最大流。一个通俗易懂的证明

​ 变式:([CEOI2008]order)(下附代码):将原边容量改为租金即可(因为租用与否仅和此工序有关)

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int f=1,w=0;char x=0;
    while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();}
    while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();}
    return w*f;
}
const int N=300010;
int n,m,sum,s,t;
int head[N],num_edge,d[N],cur[N];
struct {int next,to,dis;} edge[N*10];
inline void add(int from,int to,int dis)
{
	edge[++num_edge].next=head[from];
	edge[num_edge].dis=dis;
	edge[num_edge].to=to;
	head[from]=num_edge;
}
inline bool bfs()
{
	memset(d,0,sizeof(d));d[s]=1;
	queue<int> q;q.push(s);
	while(!q.empty())
	{
		int x=q.front();q.pop();
		for(int i=head[x];i!=-1;i=edge[i].next)
			if(!d[edge[i].to]&&edge[i].dis)
				d[edge[i].to]=d[x]+1,q.push(edge[i].to);
	}
	return d[t]?1:0;
}
inline int dfs(int pos,int dis)
{
	if(pos==t) return dis;
	for(int i=cur[pos];i!=-1;i=edge[i].next)
	{
		cur[pos]=i;
		if(d[edge[i].to]==d[pos]+1&&edge[i].dis>0)
		{
			int data=dfs(edge[i].to,min(edge[i].dis,dis));
			if(data>0)
			{
				edge[i].dis-=data;
				edge[i^1].dis+=data;
				return data;
			}
		}
	}
	return 0;
}
inline int Dinic()
{
	int ans=0;
	while(bfs())
	{
		memcpy(cur,head,sizeof(cur));
		while(int data=dfs(s,0x3f3f3f3f))
			ans+=data;
	}
	return ans;
}
int main(){
#ifndef ONLINE_JUDGE
    freopen("A.in","r",stdin);
#endif
	n=read(),m=read();s=0,t=n+m+2;
	num_edge=-1;memset(head,-1,sizeof(head));
	for(int i=1;i<=n;i++)
	{
		int w=read(),c=read();sum+=w;
		add(0,i,w),add(i,0,0);
		for(int j=1;j<=c;j++)
		{
			int id=read();w=read();
			add(i,id+n,w),add(id+n,i,0);
		}
	}
	for(int i=1,w;i<=m;i++)
		w=read(),add(i+n,t,w),add(t,i+n,0);
	printf("%d",sum-Dinic());
}

三、最小路径覆盖

​ 转化:先拆点为(v,v{'}),原图中(u)(v)有边,连(u,v{'})即可。最小路径(=)点数(-)最大匹配。

​ 变式:([SDOI2010])星际竞速:考虑跳跃,无论从哪个点跳到(i),花费均为(A[i]),所以可以建(S)(v{'}),容量为(A[i])的边。(下附代码)

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int f=1,w=0;char x=0;
    while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();}
    while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();}
    return w*f;
}
const int N=300010;
int n,m,sum,s,t,flo[N],v[N],ans;
int head[N],num_edge,d[N],cur[N];
struct {int next,to,dis,cos;} edge[N*10];
inline void add(int from,int to,int dis,int cos)
{
	edge[++num_edge].next=head[from];
	edge[num_edge].dis=dis;
	edge[num_edge].cos=cos;
	edge[num_edge].to=to;
	head[from]=num_edge;
}
inline bool bfs()
{
	memset(d,0x3f3f3f3f,sizeof(d));
	memset(v,0,sizeof(v));memset(flo,0,sizeof(flo));
	queue<int> q;q.push(s);flo[s]=1;v[s]=1;d[s]=0;
	while(!q.empty())
	{
		int x=q.front();q.pop();v[x]=0;
		for(int i=head[x];i!=-1;i=edge[i].next)
			if(d[edge[i].to]>d[x]+edge[i].cos&&edge[i].dis>0)
			{
				d[edge[i].to]=d[x]+edge[i].cos;
				flo[edge[i].to]=flo[x]+1;
				if(!v[edge[i].to]) q.push(edge[i].to),v[edge[i].to]=1;
			}
	}
	return d[t]<0x3f3f3f3f?1:0;
}
inline int dfs(int pos,int dis)
{
	if(pos==t) return dis;
	for(int i=cur[pos];i!=-1;i=edge[i].next)
	{
		int y=edge[i].to;cur[pos]=i;
		if(d[y]==d[pos]+edge[i].cos&&edge[i].dis!=0&&flo[y]==flo[pos]+1)
		{
			int data=dfs(y,min(edge[i].dis,dis));
			if(data>0)
			{
				edge[i].dis-=data;
				edge[i^1].dis+=data;
				ans+=data*edge[i].cos;
				return data;
			}
		}
	}
	return 0;
}
inline int Dinic()
{
	while(bfs())
	{
		memcpy(cur,head,sizeof(cur));
		while(int data=dfs(s,0x3f3f3f3f));
	}
	return ans;
}
int main(){
#ifndef ONLINE_JUDGE
    freopen("A.in","r",stdin);//12
	//freopen("B.in","r",stdin);//6
	//freopen("C.in","r",stdin);//230
#endif
	n=read(),m=read();s=0,t=(n<<1)+1;
	num_edge=-1;memset(head,-1,sizeof(head));
	for(int i=1,w;i<=n;i++)
		w=read(),add(s,i+n,1,w),add(i+n,s,0,-w);
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read(),w=read();
		if(v<u) swap(u,v);
		add(u,v+n,1,w),add(v+n,u,0,-w);
	}
	for(int i=1;i<=n;i++)
		add(s,i,1,0),add(i,s,0,0),add(i+n,t,1,0),add(t,i+n,0,0);
	printf("%d",Dinic());
}

四、各种奇特的建图

1、与时间有关:

其实这样的题以前考过一次,好像那次(Itst)是全场唯一一个切那道题的,现在再放一道题:

传送门:(Luogu[SCOI2007])修车

显然和时间有关,要分时间段建边。

分析性质,对于一个修车工,假设其修的车用时分别为:(T_1,T_2……,T_p),那么总等待时间为:(T_p*1+T_{p-1}*2+……+T_1*p)

可以看出一个修车工所修的第倒数(k)个车对答案的贡献为(k*T)。所以我们可以将每个修车工拆为(N)个时间段,表示倒数几个修,将每个车和拆开的点建边,费用为时间段编号乘花费时间即可。(下附代码)

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int f=1,w=0;char x=0;
    while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();}
    while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();}
    return w*f;
}
const int N=6000,M=100010;
int n,m,num_edge,s,t,ans;
int d[N],head[N],v[N],flo[N],cur[N];
struct Edge{int next,to,dis,cos;} edge[M];
inline void add(int from,int to,int dis,int cos)
{
	edge[++num_edge].next=head[from];
	edge[num_edge].dis=dis;
	edge[num_edge].cos=cos;
	edge[num_edge].to=to;
	head[from]=num_edge;
}
inline bool bfs()
{
	memset(d,0x3f3f3f3f,sizeof(d));
	memset(v,0,sizeof(v));memset(flo,0,sizeof(flo));
	queue<int> q;q.push(s);d[s]=0,v[s]=flo[s]=1;
	while(!q.empty())
	{
		int x=q.front();q.pop();v[x]=0;
		for(int i=head[x];i!=-1;i=edge[i].next)
			if(d[edge[i].to]>d[x]+edge[i].cos&&edge[i].dis>0)
			{
				d[edge[i].to]=d[x]+edge[i].cos;
				flo[edge[i].to]=flo[x]+1;
				if(!v[edge[i].to]) q.push(edge[i].to),v[edge[i].to]=1;
			}
	}
	return d[t]<0x3f3f3f3f?1:0;
}
inline int dfs(int pos,int dis)
{
	if(pos==t) return dis;
	for(int i=cur[pos];i!=-1;i=edge[i].next)
	{
		int y=edge[i].to;cur[pos]=i;
		if(flo[y]==flo[pos]+1&&d[y]==d[pos]+edge[i].cos&&edge[i].dis>0)
		{
			int data=dfs(y,min(dis,edge[i].dis));
			if(data>0)
			{
				edge[i].dis-=data;
				edge[i^1].dis+=data;
				ans+=edge[i].cos*data;
				return data;
			}
		}
	}
	return 0;
}
inline int Dinic()
{
	while(bfs())
	{
		memcpy(cur,head,sizeof(head));
		while(int data=dfs(s,0x3f3f3f3f));
	}
	return ans;
}
int main(){
#ifndef ONLINE_JUDGE
    freopen("A.in","r",stdin);//Ans=1.50
#endif
	num_edge=-1;memset(head,-1,sizeof(head));
	m=read(),n=read();s=0,t=m*n+n+1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			int T=read();
			for(int k=1;k<=n;k++)
				add(i,n*j+k,1,k*T),add(n*j+k,i,0,-k*T);
		}
	for(int i=1;i<=n;i++) add(s,i,1,0),add(i,s,0,0);
	for(int i=1;i<=n*m;i++) add(i+n,t,1,0),add(t,i+n,0,0);
	printf("%.2lf",(double)Dinic()/(double)n);
}

(To) (be) (continued...)(怎么办我好想咕)

原文地址:https://www.cnblogs.com/wo-shi-zhen-de-cai/p/11528471.html