网络流小结

注:因为风骨傲天习惯用\(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...\)(怎么办我好想咕)

posted @ 2019-09-16 17:24  风骨傲天  阅读(147)  评论(0编辑  收藏  举报