网络流

网络流

没有赘述概念和理解,想了解可以看参考资料和oi-wiki

没什么营养的板子

细节!

Dinic

  1. 建边 tot=1

  2. 反边初始容量为零 add(y,x,0)

  3. 中间任意时刻能流的量为零了,直接跳 如 &&c&&res

  4. d 数组分层用,如果不能往后面流了,直接封死 if(!flow) d[u]=-1

  5. 弧优化

dinic
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 205,M = 5005;
int n,m,s,t;
int head[N],tot=1;// 初始 tot=0,后面定反边用 
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}

namespace FLOW
{
	int T;
	int d[N],now[N];
	inline LL dfs(int u,LL res)
	{
		if(u==T) return res;
		LL flow=0;
		for(int i=now[u];i&&res;i=e[i].u)//res==0 时没有流量了,直接跳 
		{
			now[u]=i;//弧优化 
			int v=e[i].v,c=min((LL)e[i].w,res);
			if(d[v]==d[u]+1&&c)//c==0 时没流量了,直接跳 
			{
				int k=dfs(v,c);
				flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;//反边加容量,后面可以反悔 
			}
		}
		if(!flow) d[u]=-1;//不能继续增广 ,路死了 
		return flow;
	}
	inline LL dinic(int s,int t)
	{
		T=t;//注意
		LL flow=0;
		while(1)
		{
			queue<int> q;
		    memcpy(now,head,sizeof(head));
		    memset(d,-1,sizeof(d));//分层,初始为零
			q.push(s); d[s]=0;
			while(!q.empty())
			{
				int u=q.front(); q.pop();
				for(int i=head[u];i;i=e[i].u)
				{
					int v=e[i].v;
					if(d[v]==-1&&e[i].w) d[v]=d[u]+1,q.push(v);
				}
			}
			if(d[t]==-1) return flow;
			flow+=dfs(s,1e18);
		}
	}

} using namespace FLOW;
int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;i++)
	{
		int x,y,z; scanf("%d%d%d",&x,&y,&z);
		add(x,y,z); add(y,x,0);//注意,反边初始容量为 0 
	}
	printf("%lld\n",dinic(s,t));
	return 0;
}

EK

EK
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1200+5,M = 120000+5;
int n,m,s,t;
int head[N],tot=1;//从一开始
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}

LL f[N];
int pre[N];
LL EK(int s,int t)
{
	LL flow=0;
	while(1)
	{
		queue<int> q;
		memset(f,-1,sizeof(f));
		f[s]=1e18; q.push(s);//初始流量极大值
		while(!q.empty())
		{
			int u=q.front(); q.pop();
			for(int i=head[u];i;i=e[i].u)
			{
				int v=e[i].v;
				if(e[i].w&&f[v]==-1)
				{
					f[v]=min(f[u],(LL)e[i].w); pre[v]=i;//记录路径
					q.push(v);
				}				
			}
		}
		if(f[t]==-1) return flow;
		flow+=f[t];
		for(int u=t;u!=s;u=e[pre[u]^1].v) e[pre[u]].w-=f[t],e[pre[u]^1].w+=f[t];//回溯
	}
}

int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;i++)
	{
		int x,y,z; scanf("%d%d%d",&x,&y,&z);
		add(x,y,z); add(y,x,0);
	}
	printf("%lld\n",EK(s,t));
	return 0;
}

SSP

  1. spfa 出队删标记

  2. 反边容量初始为 0,费用为负。

  3. 边数不是 N<<1 !!!

ssp
#include<bits/stdc++.h>
using namespace std;
#define P pair<int,int>
const int N = 405,M = 15005;
int n,m;
int head[N],tot=1;
struct E {int u,v,w,c;} e[M<<1];//边数为M
inline void add(int u,int v,int w,int c) {e[++tot]={head[u],v,w,c}; head[u]=tot;}

int d[N],f[N],pre[N];
bool vs[N];

P ssp(int s,int t)
{
	int flow=0,cost=0;
	while(1)
	{
		queue<int> q;
		memset(d,0x3f,sizeof(d));
		memset(vs,0,sizeof(vs));
		q.push(s); d[s]=0; f[s]=1e9;//初始流量极大值 
		while(!q.empty())
		{
			int u=q.front(); q.pop(); vs[u]=0;//出队删标记
			for(int i=head[u];i;i=e[i].u)
			{
				int v=e[i].v,dis=d[u]+e[i].c; 
				if(e[i].w&&dis<d[v]) 
				{
					d[v]=dis; pre[v]=i; f[v]=min(f[u],e[i].w); //记录路径
					if(!vs[v]) vs[v]=1,q.push(v);
				}
			}
		}
		if(d[t]>1e9) return {flow,cost};
		flow+=f[t]; cost+=f[t]*d[t];
		for(int u=t;u!=s;u=e[pre[u]^1].v) e[pre[u]].w-=f[t],e[pre[u]^1].w+=f[t];//回溯
	}
}

int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y,z,w; scanf("%d%d%d%d",&x,&y,&z,&w);
		add(x,y,z,w); add(y,x,0,-w);
	}
	P ans=ssp(1,n);
	printf("%d %d\n",ans.first,ans.second);
	return 0;
}

开门大吉 (集合划分问题)

做了几道类似的题,感觉关键在于决策的并联串联限制

在不考虑限制的条件下,“串联”的决策需要在一条路径上做出一个选择,而“并联”表示决策具有独立性,每条路径都单独做出决策。

在这种由几条长链连接源点和汇点的图中,显然最小割就是最优决策。

如果再加入限制呢?

也就是规定一些边不能同时被选(或者是同时被选有额外代价),那么我们希望图中得到的效果就是:如果两条边同时被选,那么图仍没有被割成两个不相交的点集(割不动)。

所以我们可以通过在不同的长链间添加“横插边”来提供种限制,至于容量直接设置成无限大,怎么割也割不完,所以不能同时选,并且不会影响其他的条件。

注意加的限制需要考虑方向,有时候需要加双向边,也就是正反都会有代价,实际上就是正反容量初始都不为零,这样怎么 割都可以,就是双向边了。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 100005,M = 1000005;
const double del = 1e-18,inf = 1e9;
int n,m,p,TT,q,c[N];
int head[N],tot=1;
struct E {int u,v; double w;} e[M<<1];
inline void add(int u,int v,double w) {e[++tot]={head[u],v,w}; head[u]=tot; /*printf("%d %d %lf\n",u,v,w)*/;}
void init()
{
	memset(head,0,sizeof(head)); tot=1;
}
int T,now[N],d[N];

double dfs(int u,double res)
{
	if(u==T) return res;
	double flow=0;
	for(int i=now[u];i&&res>del;i=e[i].u)
	{
		now[u]=i;
		int v=e[i].v; double c=min(res,e[i].w);
		if(d[v]==d[u]+1&&c>del)
		{
			double k=dfs(v,c);
			flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
		}
	}
	if(flow<del) d[u]=-1;
	return flow;
}

double dinic(int s,int t)
{
	T=t;
	double flow=0;
	while(1)
	{
		queue<int> q;
		memset(d,-1,sizeof(d));
		memcpy(now,head,sizeof(head));
		d[s]=0; q.push(s);
		while(!q.empty())
		{
			int u=q.front(); q.pop();
			for(int i=head[u];i;i=e[i].u)
			{
				int v=e[i].v;
				if(d[v]==-1&&e[i].w>del) d[v]=d[u]+1,q.push(v);
			}
		}
		if(d[t]==-1) return flow;
		flow+=dfs(s,1e9);
		if(flow>=1e9) return flow;
	}
}

int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d",&TT);
	while(TT--)
	{
		init();
		scanf("%d%d%d%d",&n,&m,&p,&q); int s=0,t=n*m+1;
		for(int i=1;i<=p;i++) scanf("%d",&c[i]);
		for(int j=1;j<=m;j++)
		{
			for(int i=1;i<=n;i++)
			{
				double sum=0,tt=1;
				for(int k=1;k<=p;k++)
				{
					double x; scanf("%lf",&x);
					tt*=x; sum=sum+tt*c[k];
				}
				if(j==m) add((i-1)*m+j,t,sum),add(t,(i-1)*m+j,0);
				else add((i-1)*m+j,(i-1)*m+j+1,sum),add((i-1)*m+j+1,(i-1)*m+j,0);
			}
		}
		for(int i=1;i<=n;i++)
		{
			add(s,(i-1)*m+1,inf); add((i-1)*m+1,s,0);
		}
		for(int i=1;i<=q;i++)
		{
			int x,y,z; scanf("%d%d%d",&x,&y,&z);
			for(int j=1;j<=m;j++)
			{
				if(j+z<=m&&j+z>=1) add((y-1)*m+j,(x-1)*m+j+z,inf),add((x-1)*m+j+z,(y-1)*m+j,0);
				else if(j+z<1) continue;//注意有负数
				else add((y-1)*m+j,t,inf),add(t,(y-1)*m+j,0);
			}
		}
		double ans=dinic(s,t);
		if(ans>=1e9) printf("-1\n");
		else printf("%lf\n",ans);
	}
	return 0;
}

切糕(最小割离散变量)

集合划分的应用,本质一样。

不知道为什么起了个新名字,板板板,典典典。

发现在每个 \((x,y)\)\(z\) 个决策中选一个,每个 \((x,y)\) 之间互不影响(暂时不考虑额外限制)。

所以每个 \((x,y)\)\(z\) 个决策连一条长链连接源点和汇点,一共 \(xy\) 条链,然后再加入限制,挺典。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 50,inf = 1e9;
int p,q,r,D;
int a[N][N][N],cnt,mp[N][N][N];
int head[N*N*N],tot=1;
struct E {int u,v,w;} e[N*N*N<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot; /* printf("%d %d\n",u,v);*/}
int xx[4]={0,1,-1,0},yy[4]={1,0,0,-1};

int T,now[N*N*N],d[N*N*N];

int dfs(int u,int res)
{
	if(u==T) return res;
	int flow=0;
	for(int i=now[u];i&&res;i=e[i].u)
	{
		now[u]=i;
		int v=e[i].v,c=min(res,e[i].w);
		if(d[v]==d[u]+1&&c)
		{
			int k=dfs(v,c);
			flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
		}
	}
	if(flow==0) d[u]=-1;
	return flow;
}

inline int dinic(int s,int t)
{
	int flow=0; T=t;
	while(1)
	{
		queue<int> q;
		memset(d,-1,sizeof(d));
		memcpy(now,head,sizeof(head));
		d[s]=0; q.push(s);
		while(!q.empty())
		{
			int u=q.front(); q.pop();
			for(int i=head[u];i;i=e[i].u)
			{
				int v=e[i].v;
				if(e[i].w&&d[v]==-1) d[v]=d[u]+1,q.push(v);
			}
		}
		if(d[t]==-1) return flow;
		flow+=dfs(s,1e9);
	}
}

int main()
{
//	freopen("in.in","r",stdin);
//	freopen("out.out","w",stdout);
	scanf("%d%d%d%d",&p,&q,&r,&D);
	for(int k=1;k<=r;k++)
		for(int i=1;i<=p;i++)
			for(int j=1;j<=q;j++) scanf("%d",&a[k][i][j]),mp[k][i][j]=++cnt;
	int s=0,t=cnt+1;
	for(int i=1;i<=p;i++)
		for(int j=1;j<=q;j++)
		{
			add(s,mp[1][i][j],inf); add(mp[1][i][j],s,0);
			for(int k=1;k<r;k++) add(mp[k][i][j],mp[k+1][i][j],a[k][i][j]),add(mp[k+1][i][j],mp[k][i][j],0);
			add(mp[r][i][j],t,a[r][i][j]),add(t,mp[r][i][j],0);
		}
	for(int i=1;i<=p;i++)
		for(int j=1;j<=q;j++)
			for(int h=0;h<4;h++)
			{
				int x=i+xx[h],y=j+yy[h];
				if(x<1||x>p||y<1||y>q) continue;
				for(int k=D+1;k<=r;k++) add(mp[k][i][j],mp[k-D][x][y],inf),add(mp[k-D][x][y],mp[k][i][j],0);
			}
	
	printf("%d\n",dinic(s,t));
	return 0;
}

货币

题面

挺板的集合划分问题,转化问题就简单了。

\((1,i)\)\((2,i)\) 的边也看成特殊限制,也就是如果同时走了 \((1,i-1)\)\((2,i)\),那么会多 \(c_i\) 的代价。

每一个 \(i\) 可以选择在第一行走,也可以选择在第二行走,直接连 \(i\) 条长链,然后加入限制。

\(i\) 选了第一行,\(j\) 选了第二行,会多代价,连一条代价为 \(c_i\) 的横插边就好了。注意有一些双向边。

code
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 505,M = 3005;
int n,m;
int head[N],tot=1;
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}

int T,d[N],now[N];

LL dfs(int u,LL res)
{
	if(u==T) return res;
	LL flow=0;
	for(int i=now[u];i&&res;i=e[i].u)
	{
		now[u]=i;
		int v=e[i].v,c=min((LL)e[i].w,res);
		if(d[v]==d[u]+1&&c)
		{
			int k=dfs(v,c); 
			flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
		}
	}
	if(!flow) d[u]=-1;
	return flow;
}

LL dinic(int s,int t)
{
	LL flow=0; T=t;
	while(1)
	{
		queue<int> q;
		memset(d,-1,sizeof(d));
		memcpy(now,head,sizeof(head));
		d[s]=0; q.push(s);
		while(!q.empty())
		{
			int u=q.front(); q.pop();
			for(int i=head[u];i;i=e[i].u)
			{
				int v=e[i].v;
				if(d[v]==-1&&e[i].w) d[v]=d[u]+1,q.push(v);
			}
		}
		if(d[t]==-1) return flow;
		flow+=dfs(s,1e18);
	}
}

int main()
{
	freopen("currency.in","r",stdin);
	freopen("currency.out","w",stdout);
	scanf("%d%d",&n,&m);  int s=0,t=n+1;
	for(int i=1;i<n;i++)
	{
		int x; scanf("%d",&x); add(s,i,x); add(i,s,0);
	}
	for(int i=1;i<=n;i++)
	{
		int x; scanf("%d",&x);
		if(i==1) add(i,t,x),add(t,i,0);
		else if(i==n) add(s,i-1,x),add(i-1,s,0);
		else
		{
			add(i-1,i,x); add(i,i-1,x);
		}
	}
	for(int i=1;i<n;i++)
	{
		int x; scanf("%d",&x); add(i,t,x); add(t,i,0);
	}
	for(int i=1;i<=m;i++)
	{
		int x,y,z; scanf("%d%d%d",&x,&y,&z);
		add(y,x,z); add(x,y,0);
	}
	printf("%lld\n",dinic(s,t));
	return 0;
}

飞行员配对方案问题 (二分图最大匹配)

二分图最大匹配板子,考虑左右部分别连源点和汇点,容量都为一,中间的边容量也是一,,因为一个人只能连一个人。最后方案找满流的边。(注意边数很大)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 105,M = 1e5+5;
int n,m;
int head[N],tot=1;
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}

namespace FLOW
{
	int T,now[N],d[N];
	int dfs(int u,int res)
	{
		if(u==T) return res;
		int flow=0;
		for(int i=now[u];i&&res;i=e[i].u)
		{
			now[u]=i;
			int v=e[i].v,c=min(res,e[i].w);
			if(d[v]==d[u]+1&&c)
			{
				int k=dfs(v,c);
				flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
			}
		}
		if(!flow) d[u]=-1;
		return flow;
	}
	int dinic(int s,int t)
	{
		T=t;
		int flow=0;
		while(1)
		{
			queue<int> q;
			memset(d,-1,sizeof(d));
			memcpy(now,head,sizeof(head));
			d[s]=0; q.push(s);
			while(!q.empty())
			{
				int u=q.front(); q.pop();
				for(int i=head[u];i;i=e[i].u)
				{
					int v=e[i].v; 
					if(d[v]==-1&&e[i].w) d[v]=d[u]+1,q.push(v);
				}
			}
			if(d[t]==-1) return flow;
			flow+=dfs(s,1e9);
		}
	}
} using namespace FLOW;
int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d",&m,&n); n=n-m;
	for(int i=1;i<=m;i++) add(0,i,1),add(i,0,0) ;
	for(int i=1;i<=n;i++) add(i+m,n+m+1,1),add(n+m+1,i+m,0);
	int x,y;
	while(scanf("%d%d",&x,&y)&&x!=-1&&y!=-1)
		add(x,y,1),add(y,x,0);
	int ans=dinic(0,n+m+1);
	if(ans==0)
	{
		printf("No Solution!");
		return 0;
	}
	printf("%d\n",ans);
	for(int u=1;u<=m;u++)
	{
		for(int i=head[u];i;i=e[i].u)		
		{
			int v=e[i].v; if(e[i^1].w==0||e[i].w||v==0||v==n+m+1) continue;
			printf("%d %d\n",u,v);
		}
	}
	return 0;
}

方格取数问题(二分图最大(权)独立集)

五倍经验:王者之剑骑士共存问题攻击装置长脖子鹿放置

方格取数是二分图最大权匹配,网络流的做法就是统计总价值,然后用最小割刻画最小的冲突代价,然后相减。

其他的都是二分图最大匹配,就是权值为 \(1\).

具体的建图方式:

将所有点分成两部分,保证每一部分内的点之间没有冲突(二分图),分别用源点和汇点连边,容量为价值,中间冲突用无穷大的容量刻画。

这样是否删减的信息只在与源点和汇点的边上,中间的边只描述冲突,不参与统计(割不动)。

注意第一步要把所有点分成两部分,一般直接黑白交错(横纵坐标之和的奇偶性),可以满足”不相邻“和”日字“限制。

长脖子鹿放置出现了奇葩的”目字“限制,考虑直接按行号奇偶分,就做完了。

P2774
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+5,M = 1e5+5,inf = 1e9;
int n,m;
int head[N],tot=1,a[N],mp[105][105],num,ans;
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}
int xx[4]={0,0,1,-1},yy[4]={1,-1,0,0};
int T,now[N],d[N];
int dfs(int u,int res)
{
	if(u==T) return res;
	int flow=0;
	for(int i=now[u];i&&res;i=e[i].u)
	{
		now[u]=i;
		int v=e[i].v,c=min(e[i].w,res);
		if(d[v]==d[u]+1&&c)
		{
			int k=dfs(v,c);
			flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
		}
	}
	if(!flow) d[u]=-1;
	return flow;
}
int dinic(int s,int t)
{
	T=t;int flow=0;
	while(1)
	{
		queue<int> q;
		memset(d,-1,sizeof(d));
		memcpy(now,head,sizeof(head));
		d[s]=0; q.push(s);
		while(!q.empty())
		{
			int u=q.front();  q.pop();
			for(int i=head[u];i;i=e[i].u)
			{
				int v=e[i].v;
				if(d[v]==-1&&e[i].w) d[v]=d[u]+1,q.push(v);
			}
		}
		if(d[t]==-1) return flow;
		flow+=dfs(s,1e9);
	}
}
int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++) 
		{
			mp[i][j]=++num;
			scanf("%d",&a[num]); ans=ans+a[num];
		}
	}	
	int s=0,t=num+1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if((i+j)&1)
			{
				add(s,mp[i][j],a[mp[i][j]]);
				add(mp[i][j],s,0);
				for(int k=0;k<4;k++)
				{
					int x=i+xx[k],y=j+yy[k];
					if(x<1||x>n||y<1||y>m) continue;
					add(mp[i][j],mp[x][y],inf);
					add(mp[x][y],mp[i][j],0);
				}
			}
			else
			{
				add(mp[i][j],t,a[mp[i][j]]);
				add(t,mp[i][j],0);
			}
		}
	}
	printf("%d\n",ans-dinic(s,t));
	return 0;
}
P5030
#include<bits/stdc++.h>
using namespace std;
const int N = 4e4+5,M = 4e5+5,inf = 1e9;
int n,m,S;
int head[N],tot=1,a[N],mp[205][205],num,ans;
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}
int xx[8]={-3,-1,1,3,3,1,-1,-3},yy[8]={1,3,3,1,-1,-3,-3,-1};
int T,now[N],d[N];
int dfs(int u,int res)
{
	if(u==T) return res;
	int flow=0;
	for(int i=now[u];i&&res;i=e[i].u)
	{
		now[u]=i;
		int v=e[i].v,c=min(e[i].w,res);
		if(d[v]==d[u]+1&&c)
		{
			int k=dfs(v,c);
			flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
		}
	}
	if(!flow) d[u]=-1;
	return flow;
}
int dinic(int s,int t)
{
	T=t;int flow=0;
	while(1)
	{
		queue<int> q;
		memset(d,-1,sizeof(d));
		memcpy(now,head,sizeof(head));
		d[s]=0; q.push(s);
		while(!q.empty())
		{
			int u=q.front();  q.pop();
			for(int i=head[u];i;i=e[i].u)
			{
				int v=e[i].v;
				if(d[v]==-1&&e[i].w) d[v]=d[u]+1,q.push(v);
			}
		}
		if(d[t]==-1) return flow;
		flow+=dfs(s,1e9);
	}
}
int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d%d",&n,&m,&S);
	for(int i=1;i<=S;i++)
	{
		int x,y; scanf("%d%d",&x,&y);
		mp[x][y]=-1;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(mp[i][j]!=-1) mp[i][j]=++num,ans++;
		}
	}
	int s=0,t=num+1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++) if(mp[i][j]!=-1)
		{
			if((i)&1)
			{
				add(s,mp[i][j],1);
				add(mp[i][j],s,0);
				for(int k=0;k<8;k++)
				{
					int x=i+xx[k],y=j+yy[k];
					if(x<1||x>n||y<1||y>n||mp[x][y]==-1) continue;
					add(mp[i][j],mp[x][y],inf);
					add(mp[x][y],mp[i][j],0);
				}
			}
			else
			{
				add(mp[i][j],t,1);
				add(t,mp[i][j],0);
			}
		}
	}
	printf("%d\n",ans-dinic(s,t));
	return 0;
}

圆桌问题(二分图多重匹配)

板,好像是扩展版的最大匹配。转化问题为每组匹配 \(r_i\) 个桌子,然后做完了。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1000+5,M = 1e5+5;
int n,m,s,t;
int head[N],tot=1,sum;
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}

int T,now[N],d[N];
int dfs(int u,int res)
{
	if(u==T) return res;
	int flow=0;
	for(int i=now[u];i&&res;i=e[i].u)
	{
		now[u]=i;
		int v=e[i].v,c=min(res,e[i].w);
		if(d[v]==d[u]+1&&c)
		{
			int k=dfs(v,c);
			flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
		}
	}
	if(!flow) d[u]=-1;
	return flow;
}
int dinic(int s,int t)
{
	int flow=0; T=t;
	while(1)
	{
		queue<int> q;
		memset(d,-1,sizeof(d));
		memcpy(now,head,sizeof(head));
		d[s]=0; q.push(s);
		while(!q.empty())
		{
			int u=q.front(); q.pop();
			for(int i=head[u];i;i=e[i].u)
			{
				int v=e[i].v;
				if(d[v]==-1&&e[i].w) d[v]=d[u]+1,q.push(v);
			}
		}
		if(d[t]==-1) return flow;
		flow+=dfs(s,1e9);
	}
}

int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d%d",&m,&n); s=0; t=n+m+1;
	for(int i=1;i<=m;i++)
		for(int j=m+1;j<=m+n;j++)
			add(i,j,1), add(j,i,0);
	for(int i=1;i<=m;i++)
	{
		int x; scanf("%d",&x); sum+=x;
		add(s,i,x); add(i,s,0);
	}
	for(int i=m+1;i<=m+n;i++)
	{
		int x; scanf("%d",&x);
		add(i,t,x); add(t,i,0);
	}
	int ans=dinic(s,t);
	if(ans<sum) printf("0\n");
	else 
	{
		printf("1\n");
		for(int u=1;u<=m;u++)
		{
			for(int i=head[u];i;i=e[i].u)
			{
				int v=e[i].v;
				if(e[i].w==0&&e[i^1].w) printf("%d ",v-m);
			}
			printf("\n");
		}
	}
	return 0;
}

餐巾计划问题 (建模)

  • 流量守恒
  • 最大流等于最小割
  • 拆点

首先发现干净的餐巾不能作为脏的继续流,所以把每天的餐巾分成两部分,干净的和脏的,这样就方便进行转化操作了。

注意每天流进多少干净的餐巾,就会流出多少脏的餐巾。这就恰好对应了网络流的一个重要的性质:流量守恒

于是,不如直接让源点向每天流出固定的脏餐巾,让汇点收入固定的干净的餐巾(这里其实是一个逆向的过程)。

这样既满足了流量守恒(源点流出的一定等于汇点流入的),又使图的最小割(满流的边)恰好为所需要的餐巾数。

……其实在这种定义之下,可以凭空出现一个和S,T不通的环流。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 4e3+5,M = 4e6+5,inf = 1e9;
int n,s,t;
int head[N],tot=1;
struct E {int u,v,w,c;} e[M<<1];
inline void add(int u,int v,int w,int c) {e[++tot]={head[u],v,w,c}; head[u]=tot;}

int pre[N];
long long d[N],f[N];
bool vs[N];
long long ssp(int s,int t)
{
	long long cost=0;
	while(1)
	{
		queue<int> q;
		memset(d,0x3f,sizeof(d));
		memset(vs,0,sizeof(vs));
		d[s]=0; q.push(s); f[s]=inf;
		while(!q.empty())
		{
			int u=q.front(); q.pop(); vs[u]=0;
			for(int i=head[u];i;i=e[i].u)
			{
				int v=e[i].v,dis=d[u]+e[i].c; 
				if(dis<d[v]&&e[i].w)
				{
					d[v]=dis; f[v]=min(f[u],(long long)e[i].w); pre[v]=i;
					if(!vs[v]) vs[v]=1,q.push(v);
				}
			}
		}
		if(d[t]>1e9) return cost;
		cost+=d[t]*f[t];
		for(int u=t;u!=s;u=e[pre[u]^1].v) e[pre[u]].w-=f[t],e[pre[u]^1].w+=f[t];
	}
}

int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scanf("%d",&n); s=0; t=2*n+1;
	for(int i=1;i<=n;i++)
	{
		int x; scanf("%d",&x);
		if(i+1<=n)add(i,i+1,inf,0), add(i+1,i,0,0);
		add(s,i,x,0); add(i,s,0,0);
		add(i+n,t,x,0); add(t,i+n,0,0);
	}
	int p,d1,d2,v1,v2;
	scanf("%d%d%d%d%d",&p,&d1,&v1,&d2,&v2);
	for(int i=1;i<=n;i++)
	{
		add(s,i+n,inf,p); add(i+n,s,0,-p);
		if(i+d1<=n) add(i,i+d1+n,inf,v1),add(i+d1+n,i,0,-v1);
		if(i+d2<=n) add(i,i+d2+n,inf,v2),add(i+d2+n,i,0,-v2);
	}
	printf("%lld\n",ssp(s,t));
	return 0;
}

参考资料

网络流,二分图与图的匹配

posted @ 2024-11-20 08:29  ppllxx_9G  阅读(28)  评论(2编辑  收藏  举报