「网络流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("
");
}
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
",--m);
memset(vis,0,sizeof vis);
for(int i=1;i<=m;++i)
if(!vis[i])
Print(i);
return 0;
}
谢谢阅读。