「网络流24题」 4. 魔术球问题

「网络流24题」 4. 魔术球问题

<题目链接>


最小路径覆盖。

给定了柱子数n(最小路径覆盖数)以及放球条件(建边条件),求最多有多少个球(最多有多少个点可以满足这个最小路径覆盖数)。

枚举球的数量。

每来一个球(点)m,枚举1..m-1的每个点i,若i+m满足建边条件(和为完全平方数)则按以下方式建边——

套路拆点,每个点i拆成Xi、Yi,对于一组i、m,连Xi<->Y(m+5000)双向,跑匈牙利算出最大匹配。

根据二分图相关定理:最小路径覆盖数=点数-最大匹配数。

算出当前图的最小路径覆盖k,与给定柱子数比较。

  • k<n,继续加球。
  • k=n,可能还有更大的答案,继续加球。
  • k>n,m-1就是答案,停止加球。

输出路径,遍历1..m-1每个X部点,向其匹配点走,直到无路可走,沿路标记为已遍历。

已遍历过的X部点不再遍历。

解释下为什么需要双向边。

如图(乱画的),来了一个m点后,紫色边是新加的边。

为避免TLE,我们不是清空整个图的匹配信息重跑匈牙利,而是在原匹配的基础上以m为起点进行增广。

这就需要我们从Y部的点开始——这就是建双向边的原因。

不可以直接连Y部->X部,因为这会导致你无法沿匹配点输出路径。

综上。二分图最大匹配,当然匈牙利。我爱匈牙利。

#include <cmath>
#include <cstdio>
#include <cstring>
const int MAXN=10010,MAXM=200010,MAXP=5000;
bool s_num[MAXN],vis[MAXN];
int n,m,cnt,ans,head[MAXN],match[MAXN];
struct edge
{
	int nxt,to;
}e[MAXM];
void AddEdge(int u,int v)
{
	e[++cnt].nxt=head[u];
	e[cnt].to=v;
	head[u]=cnt;
}
void AddEdges(int u,int v)
{
	AddEdge(u,v);
	AddEdge(v,u);
}
bool S_Num(int x)
{
	double t;
	return s_num[x] ? s_num[x] : (t=sqrt(x))==int(t);
}
bool DFS(int u)
{
	for(int i=head[u],v;i;i=e[i].nxt)
		if(!vis[v=e[i].to])
		{
			vis[v]=1;
			if(!match[v] || DFS(match[v]))
			{
				match[u]=v,match[v]=u;
				return 1;
			}
		}
	return 0;
}
void Print(int x)
{
	x+=MAXP;
	do
		printf("%d ",x=x-MAXP);
	while(vis[x]=1,x=match[x]);
	printf("\n");
}
int main(int argc,char *argv[])
{
	scanf("%d",&n);
	do
	{
		int t=++m+MAXP;
		for(int i=1;i<m;++i)
			if(S_Num(i+m))
				AddEdges(i,t);
		memset(vis,0,sizeof vis);
		ans+=DFS(t);
	}
	while(m-ans<=n);
	printf("%d\n",--m);
	memset(vis,0,sizeof vis);
	for(int i=1;i<=m;++i)
		if(!vis[i])
			Print(i);
	return 0;
}

谢谢阅读。

posted @ 2018-01-04 20:25  Capella  阅读(295)  评论(0编辑  收藏  举报

谢谢光临