「网络流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点后,紫色边是新加的边。

0060lm7Tly1fn4txu7riyj305008cmwz.jpg

为避免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;
}

谢谢阅读。

转载于:https://www.cnblogs.com/Capella/p/8195728.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值