网络流二十四题

开始了我的网络流 \(24\) 题之旅,写在一起到时候方便一起复习哦。

其实这并不是真的二十四题,有一些过于水的我就不写上来了。然后有的代码太水了就不写了。

感觉这些题目还是比较基础的,方法却值得借鉴!

https://www.luogu.com.cn/problem/list?keyword=&tag=332&page=1

剩余题目: 航空路线问题、火星探险问题以下

星际转移问题

题目描述

点此看题

解法

你发现时间是最大的障碍,因为对于不同的时间飞船的行驶是不同的。

但是你发现数据小得一批,如果我们每次都运一个人的话,并且花最多的时间等飞船,可以计算出时间的上界是 \(n^2m\) ,这给了我们一些暴力思想的可能,不妨在每个时间都建出这张图,构图方法如下:

  • 每个时间的月球都连向一个汇点,边权为 \(inf\)
  • 把地球直接当成原点。
  • 每个时间的点都连向下一个时间的该点,边权为 \(inf\) ,表示等待。
  • 飞船我们知道是连接那两个点了,把这两个点连边,边权为 \(h[i]\)

一开始我们可以用并查集判断有无解,然后我们边枚举答案边构图,边跑最大流。如果最大流大于等于 \(k\) 就表示可以把 \(k\) 个人运到终点了,得到了答案。好像不怎么难啊

#include <cstdio>
#include <queue>
using namespace std;
const int N = 55;
const int M = 200005;
const int inf = 0x3f3f3f3f;
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,m,k,mx,tot,ans,s,t,f[M],cur[M],dis[M],h[N],r[N],fa[N],a[N][N];
struct edge
{
	int v,c,next;
}e[2*M];
int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
void merge(int u,int v)
{
	int x=find(u),y=find(v);
	fa[x]=y;
}
void add(int u,int v,int c)
{
	e[++tot]=edge{v,c,f[u]},f[u]=tot;
	e[++tot]=edge{u,0,f[v]},f[v]=tot;
}
bool bfs()
{
	queue<int> q;
	for(int i=1;i<=ans*(n+1);i++) dis[i]=0;
	dis[t]=0;
	q.push(s);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		if(u==t) return 1;
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v;
			if(!dis[v] && e[i].c>0)
			{
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return 0;
}
int dfs(int u,int ept)
{
	if(u==t) return ept;
	int flow=0;
	for(int &i=cur[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(dis[v]==dis[u]+1 && e[i].c>0)
		{
			int tmp=dfs(v,min(ept,e[i].c));
			if(!tmp) continue;
			ept-=tmp;
			e[i].c-=tmp;
			e[i^1].c+=tmp;
			flow+=tmp;
			if(!ept) break;
		}
	}
	return flow;
}
int main()
{
	n=read();m=read();k=read();
	s=0;t=M-2;tot=1;
	for(int i=1;i<=n+2;i++) fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		h[i]=read();r[i]=read();
		for(int j=0;j<r[i];j++)
		{
			a[i][j]=read();
			if(a[i][j]==0) a[i][j]=n+1;
			if(a[i][j]==-1) a[i][j]=n+2;
			if(j) merge(a[i][j-1],a[i][j]);
		}
	}
	if(find(n+1)!=find(n+2)) {puts("0");return 0;}
	for(ans=1;;ans++)
	{
		add(s,(ans-1)*(n+1)+n+1,inf);
		for(int i=1;i<=m;i++)
		{
			int x=(ans-1)%r[i],y=ans%r[i];
			if(a[i][x]==n+2) x=t;
			else x=(ans-1)*(n+1)+a[i][x];
			if(a[i][y]==n+2) y=t;
			else y=ans*(n+1)+a[i][y];
			add(x,y,h[i]);
		}
		while(bfs())
		{
			for(int i=0;i<=ans*(n+1);i++) cur[i]=f[i];
			cur[t]=f[t];mx+=dfs(s,inf);
		}
		if(mx>=k) {printf("%d\n",ans);return 0;}
		for(int i=1;i<=n+1;i++)
			add((ans-1)*(n+1)+i,ans*(n+1)+i,inf); 
	}
}

太空飞行计划问题

题目描述

点此看题

最大权闭合子图问题,有 \(n\) 个正权点,\(m\) 个负权点,正权点和负权点之间有边,表示选了这个正权点之后必须选相连的负权点,如果满足这个条件就称为 闭合 ,求最大权值。

\(n,m\leq50\)

解法

这是一个经典模型,在题目描述里面已经介绍了一些概念,我们这样建图:

  • 源点连接正权点,容量为正权点权值。
  • 原来的边给一个 \(inf\) 的容量。
  • 负权点连接汇点,容量为负权点权值的绝对值。

然后求出最小割,答案是正权点权值 \(-\) 最小割。

现在来证明为什么,要明确一个至关重要的结论:一个点要么和 \(s\) 联通,要么和 \(t\) 联通 。首先中间些容量为 \(inf\) 是断不掉的,不用管它们,考虑如果一个点既不能连向 \(s\) 又不能连向 \(t\) ,如果是正权点,那么可以不断掉和 \(s\) 的连边,如果是负权点可以不断掉和 \(t\) 的连边,由于是最小割,但是在同样在不连通的情况下却多断了点,矛盾,反证成立。

然后考虑正权点和 \(s\) 不断边的意义是选取这个正权点,负权点不断边的意义是不选这个负权点,由于要保证不连通性那么选了正权点他相连的负权点就不会选,所以满足了闭合的条件。然后考虑是否满足最大化答案,答案可以表示为正权点权和 \(-\) 没选取的正权点 \(-\) 选取了的负权点。发现我们求的就是 \(\min\{\) 没选取的正权点 \(+\) 选取了的负权点 \(\}\) ,所以也满足最大哦。

这个模型挺有意思的,但是如果不知道这个模型做题就有点麻烦,其实可以最大流和最小割都试一试,看哪个满足题目条件,也可以把点的顺序交换一下(一开始我源点连器材就死活想不出来)

最小路径覆盖问题

题目描述

点此看题

解法

这道题看到没什么思路,但是我们可以用 调整法 。也就是一开始的路径都是只经过单点的,我们把它调整得更优。很多题都是这样的,没什么思路的时候一定要试一试调整法,它的解决范围是很广的。

考虑用网络流来实现这个调整的过程,我们把每条路径拆成 \(y\) 前端和 \(x\) 后端。如果 \((i,j)\) 有一条有向边那么就可以把 \(i\)\(x\) 连向 \(j\)\(y\) ,如果这一条边上有流量那么就表示我们把 \(i\)\(j\) 接在一起了,构图方法如下:

  • 源点连 \(y\) 端,容量为 \(1\)
  • \(y\)\(x\) 就像上面说的那样连
  • \(x\) 端连汇点,容量为 \(1\)

最后会是 \(y-x-y-x-y-x\) 之类的路径覆盖,如果我们跑最大流的话就可以得到最小路径覆盖。输出方案数的话就看哪条边是使用过的,如果你弄懂了上面那些肯定就会输出方案啦!

魔术球问题

题目描述

点此看题

解法

做到这道题我才发现一个很重要的东西:有些题是不能直接根据题目意义网络流的,要先把他用图论表示出来,然后这个图论问题的解决可能使用到网络流这位大佬的思路实在是太强了!

首先柱子是辣鸡,基本上没卵用,你会发现关键还是在数,我们把具有完全平方数关系的两个点 \((x,y)\) 连一条有向边,就可以得到一张图,要注意的是 \(x<y\) ,这种图称为 隐式图 ,比如样例的图长这样:

那么问题变成了我们选出 $n $ 条路径使得能覆盖 \([1,ans]\) 的所有点,貌似不是很好做。因为 \(n\) 很小,所以到了后面选数就很容易没位置填,所以 \(ans\) 不会很大。我们可以枚举 \(ans\) ,那么问题就变成了 \([1,ans]\) 的点都能被覆盖的路径数 \(\leq n\) 的最大的 \(ans\) ,这就转化成了 最小路径覆盖问题

最长不下降子序列问题

题目描述

点此看题

解法

我觉得上一道题那位大佬介绍的方法真是太好用啦!

首先跑 \(dp\) ,设 \(dp[i]\) 为以 \(i\) 为结尾的最长不下降子序列长度。然后我们构建出图论模型,对于 \(i<j,a[i]\leq a[j]\),如果 \(dp[j]+1=dp[i]\) 那么我们就把连一条 \((i,j)\) 的边,问题转化成了:取出若干条不相交的长度为 \(\max\) 的路径,要求最大化路径数量。

可以用网络流来完成这个最大化的过程,我们把每个点分成 \(x\) 端和 \(y\) 端,这样建图:

  • 我们把源点向 \(dp[i]=1\) 的点的 \(x\) 端连边,表示可以作为起点,容量为 \(1\)
  • 我们把每个的 \(x\) 端向 \(y\) 端连边,容量是 \(1\) ,表示路径经过了这个点。
  • 对于原图中的边 \((i,j)\),连 \(i\)\(y\) 端和 \(j\)\(x\) 端,容量是 \(1\) ,表示这条边连接两个点,扩展这条路径。
  • 我们把 \(dp[i]=\max\) 点的 \(y\) 端向汇点连边,容量为 \(1\) ,表示可以作为路径的结束点。

然后第二问就解决了,第三问就修改网络中的这些边容量无限: \((s,x_1),(x_1,y_1),(x_n,y_n),(y_n,t)\) ,就表示这两个点可以无限使用,写着很简单,代码就不给了。

方格取数问题

题目描述

点此看题

解法

套路题,遇到矩阵之类的题都要注意一下了,我们可以按奇偶性来划分原矩阵,然后把这个问题转化为二分图。也就是 \(i+j\) 为奇数的就归到 \(x\) 部,\(i+j\) 偶数就归到 \(y\) 部,本题 \(x\) 部连 \(y\) 部表示选了 \(x\) 部的这个点就不能选 \(y\) 部的那个点。这个题感觉和 最大权闭合子图问题有点像 ,我们可以这样构图:

  • 原点连 \(x\) 部,容量就是他的权值。
  • \(y\) 部连汇点,容量是他的权值。
  • \(x,y\) 中有不能共存关系的点连边,容量 \(inf\)

然后跑最小割,还是因为一个点必须和 \(x\) 联通或者 \(y\) 联通,断掉某一条边就表示不选这条边的对应点。那么要求断掉的边权值最小,那么就跑最小割。

餐巾计划问题

题目描述

点此看题

解法

特别经典的模型,而且特别有用,我给它起了个名字:补流模型

为什么我们要这样取名呢?当我们把餐巾当成容量,然后跑费用流。如果我们流到汇点之后,这个流量就消失了,但是我们还需要这个流量,因为餐巾是可以被洗干净的,所以我们选择从 源点补充这些被汇点拿走的流量 ,就达到了重复利用餐巾的效果,具体这样建图:

  • 每天拆成早上和晚上(晚上用来补流),原点向每天晚上提供 \(x\) 条脏毛巾,费用为 \(0\) 。每天早上向汇点连边,容量为 \(x\) ,费用为 \(0\)
  • 每天晚上向下一天晚上连边,容量无限,费用为 \(0\) ,表示不处理脏毛巾。
  • 源点向每天早上连边,容量为 \(inf\) ,费用为买一条新毛巾的花费。
  • 每天晚上向对应的早上连边,容量为无限,费用为快洗 \(/\) 慢洗的费用,注意不要超过 \(n\)

最长k可重区间集问题

题目描述

点此看题

解法

[NOI2008]志愿者招募 是一个模型,我们可以把区间看成 \([l_i,r_i)\) ,这样我们就只用考虑整数点了,本题的要求是对于每个点不能超过 \(k\) 次覆盖。

我们讲所有整数点排成一排,源点连第一个整数点,汇点连第二个整数点,相邻两个连边,容量为 \(k\) ,费用为 \(0\) ,这是一开始的状态。

然后考虑加入我们的区间,我们讲 \(l_i\)\(r_i\) 连一条容量为 \(1\) ,费用为 \(r_i-l_i\) 的边,表示使用了这个区间就会给 \([l_i,r_i)\) 都减少 \(1\) 的流量。由于流量是 \(k\) 且不可能减到负数,所以限制成功地在图上被表示了出来。然后发现本题只有端点的整数点有用,所以一开始需要离散化。

最长k可重线段集问题

题目描述

点此看题

解法

和上面的那一道题一样,就是长度的计算方式变了一下?

不不不,我们上一道题保证了 \(l<r\) ,但是这道题会出现 \(l=r\) 的情况。对于线段 \((l,l)\)\((l,r)\) 理应在 \(l\) 点不相交,但是我们把管辖的区间设为 \([l,l)\)\([l,r)\) 就会判成相交了,所以并不完全等价。

遇到这种冲突我们考虑 扩域 ,把每个线段变成 \((2l,2r)\) ,对于 \(l=r\) ,把他管辖的区间设为 \([2l,2l+1)\),对于其他线段,要避免判成相交,我们就把它管辖的区间设为 \([2l+1,2r)\) ,就很巧妙地解决了冲突。

posted @ 2021-01-13 14:54  C202044zxy  阅读(278)  评论(0编辑  收藏  举报