网络流24题——魔术球问题 luogu 2765

Posted leozhang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络流24题——魔术球问题 luogu 2765相关的知识,希望对你有一定的参考价值。

题目描述:这里

这道题是网络流问题中第一个难点,也是一个很重要的问题

如果直接建图感觉无从下手,因为如果不知道放几个球我就无法得知该如何建图(这是很显然的,比如我知道$1+48=49=7^2$,可是我都不知道是否能放到第48个球,那我怎么知道如何建边呢?)

所以这时就体现出了一个很重要的想法:枚举答案!!!

我们知道,正常有二分答案的做法,可以二分一个答案然后检验

这里用类似的想法,但由于答案比较小而且建图更方便,所以我们直接从小往大枚举答案即可

之所以建图更方便,是因为如果我们从小向大枚举答案,那么原先建好的边是不用动的,因为原先的球一定要放,所以我们只需研究新来的球就可以了

而且还有一个好处,就是这样的话我们只需在残余网络上跑最大流,所以速度会更快一些?(口胡)

这里还有一个问题:如何建图?

直接从源点向新来的球连边,然后由新来的球向汇点连边,容量为1?

那这最大流不是要多少有多少的吗......

所以这样做显然不正确

正确的做法:把一个球拆成两个(设为x与y),然后由源点向x连边,容量为1,由y向汇点连边,容量为1,

接下来,我们找出所有满足与当前球编号和为完全平方数的球,将当前球的x点向那些球的y点连边,容量为1

然后每次在残余网络上跑最大流,如果最大流为0则说明需要新加一个柱子,加到柱子数超过给出的即结束

稍微解释一下这种做法的原因:我们把球拆成两个点以后,可以看做是一个球的上下两面,x表示与原先有的球相接,y表示在它上面放新的球的能力。

这样的话,我们把新来的x与原先合法的y相连后跑最大流,如果新的最大流不为0,说明这个新来的球能成功地放在一个原有的球上(这是因为最大流不为0说明了一条原来由某个y指向汇点的边本来流量是0,但现在流量变成了1而且保证了原有流量不变,这也就说明新来的球放在了一个柱上)

那么就自然是合法的了

然后是下一个问题(所以说这道题是好题,因为有很多个问题)

如何输出方案?

基于上面的解释,输出方案就变得简单了:我们从小往大枚举每个球,如果这个球还没被放在一个柱上则新开一个柱,然后顺着这个球的y点向下寻找,每找到一个没放下的点就放在这个柱上即可

可能说的有些抽象,具体解释一下:

我们从y点向外找边,是因为从y出发的边只要终点不是汇点那么一定是反向边!

如果反向边容量不为0,说明对应的正向边有1的流量,也就说明这个点之上被放上了一个点!

那么我们只需找出这个点,然后向下递归即可

注意找到一个点即可结束本层的寻找,因为一个球上最多只能放一个球啊

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
const int inf=0x3f3f3f3f;
struct Edge
{
	int next;
	int to;
	int val;
}edge[2000005];
int head[10005];
bool used[10005];
int cur[10005];
int cnt=1;
int st,ed;
int n;
int dis[10005];
void init()
{
	memset(head,-1,sizeof(head));
	cnt=1;
}
void add(int l,int r,int w)
{
	edge[cnt].next=head[l];
	edge[cnt].to=r;
	edge[cnt].val=w;
	head[l]=cnt++;
}
int ide(int x)
{
	return (x&1)?x+1:x-1;
}
bool bfs()
{
	memcpy(cur,head,sizeof(head));
	memset(dis,0,sizeof(dis));
	queue <int> M;
	M.push(st);
	dis[st]=1;
	while(!M.empty())
	{
		int u=M.front();
		M.pop();
		for(int i=head[u];i!=-1;i=edge[i].next)
		{
			int to=edge[i].to;
			if(edge[i].val&&!dis[to])dis[to]=dis[u]+1,M.push(to);
		}
	}
	return dis[ed];
}
int dfs(int x,int lim)
{
	if(x==ed)return lim;
	int ret=0;
	for(int i=cur[x];i!=-1;i=edge[i].next)
	{
		int to=edge[i].to;
		if(edge[i].val&&dis[to]==dis[x]+1)
		{
			int temp=dfs(to,min(lim,edge[i].val));
			if(temp)
			{
				lim-=temp;
				ret+=temp;
				edge[i].val-=temp;
				edge[ide(i)].val+=temp;
				if(!lim)break;
			}
		}
		cur[x]=i;
	}
	return ret;
}
int dinic()
{
	int ret=0;
	while(bfs())ret+=dfs(st,inf);
	return ret;
}
void print(int x)
{
	used[x]=1;
	printf("%d ",x);
	for(int i=head[(x<<1)|1];i!=-1;i=edge[i].next)
	{
		int to=edge[i].to;
		if(!edge[i].val||used[to>>1]||to==ed)continue;
		print(to>>1);
		break;
	}
}
int main()
{
	scanf("%d",&n);
	init();
	int tot;
	st=0,ed=1;
	int s=0;
	for(tot=1;tot;tot++)
	{
		add(st,tot<<1,1);
		add(tot<<1,st,0);
		add((tot<<1)|1,ed,1);
		add(ed,(tot<<1)|1,0);
		for(int j=1;j*j<2*tot;j++)
		{
			if(j*j>tot)add(tot<<1,((j*j-tot)<<1)|1,1),add(((j*j-tot)<<1)|1,tot<<1,0);
		}
		if(!dinic())s++;
		if(s>n)break;
	}
	printf("%d\n",tot-1);
	for(int i=1;i<tot;i++)
	{
		if(used[i])continue;
		print(i);
		printf("\n");
	}
	return 0;
}

  

以上是关于网络流24题——魔术球问题 luogu 2765的主要内容,如果未能解决你的问题,请参考以下文章

洛谷2765:[网络流24题]魔术球问题——题解

luogu P2765 魔术球问题 网络流24 最小路径问题

P2765 魔术球问题(网络流24题)

「网络流 24 题」魔术球 的贪心做法和证明

「网络流 24 题」魔术球 的贪心做法和证明

魔术球问题