[网络流24题]魔术球问题

题目描述

将所有球看做点,在每根柱子上放球就是下边的点向上边的点连边,可以连边的条件是两球编号之和为完全平方数。

再把这n跟柱子看做是n条路径,问题也就转换成了用n条路径覆盖所有的点,也就是最小路径覆盖问题。

//最小路径覆盖数随着点数的增加不会递减,满足二分的性质,但是二分时要重新构图,所以不如直接顺序枚举答案,这样可以利用上次的残量网络,降低了复杂度。

对于新枚举到的球,有两种选择:

1.放到已经有球的柱子上

2.自己单独开辟一根柱子(柱子数不超过n)

所以将这个新的球与已经放好的球连边(需满足条件),重新跑一遍最大流,这时若没有新流,则说明这个新球不能放在任何一个已经有球的柱子上。

//num数组记录每根柱子最下边的球的编号,用于输出方案。

#include<complex>
#include<cstdio>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=3507;
struct node{
    int v,f,nxt;
}e[N*N];
int n,Enum=1,s,t,tot;
int front[N],cur[N],deep[N],num[N],path[N];
int q[N];
bool vis[N];
void Insert(int u,int v)
{
    e[++Enum].v=v;e[Enum].f=1;e[Enum].nxt=front[u];front[u]=Enum;
    e[++Enum].v=u;e[Enum].nxt=front[v];front[v]=Enum;
}
bool bfs()
{
    for(int i=0;i<=t;i++)
    {
        deep[i]=0;
        cur[i]=front[i];
    }
    int head=1,tail=0,u,v;
    deep[s]=1;q[++tail]=s;
    while(head<=tail)
    {
        u=q[head++];
        for(int i=front[u];i;i=e[i].nxt)
        {
            v=e[i].v;
            if(!deep[v] && e[i].f)
            {
                deep[v]=deep[u]+1;
                if(v==t)return 1;
                q[++tail]=v;
            }
        }
    }
    return 0;
}
int dfs(int x,int cur_flow)
{
    if(x==t)return cur_flow;
    int rest=cur_flow,v;
    for(int &i=cur[x];i;i=e[i].nxt)
    {
        v=e[i].v;
        if(deep[v]==deep[x]+1 && e[i].f && rest)
        {
            int new_flow=dfs(v,min(e[i].f,rest));
            e[i].f-=new_flow;
            e[i^1].f+=new_flow;
            rest-=new_flow;
            if(v!=t)path[x>>1]=v>>1;
            if(!rest)return cur_flow;
        }
    }
    deep[x]=0;
    return cur_flow-rest;
}
int Dinic()
{
    int res=0;
    while(bfs())
        res+=dfs(s,INF);
    return res;
}
int main()
{
    scanf("%d",&n);
    s=0;t=N-1;
    int cnt=0;
    while(cnt<=n)
    {
        tot++;
        Insert(s,tot<<1);Insert(tot<<1|1,t);
        int tmp=sqrt(tot);
        for(int i=tmp+1;i*i<tot*2;i++)
            Insert((i*i-tot)<<1,tot<<1|1);
        if(!Dinic())num[++cnt]=tot;
    }
    printf("%d\n",tot-1);
    for(int i=1;i<=n;i++)
    {
        if(vis[num[i]])continue;
        int tmp=num[i];
        vis[tmp]=1;
        while(tmp)
        {
            printf("%d ",tmp);
            tmp=path[tmp];
            vis[tmp]=1;
        }
        puts("");
    }
    return 0;
}

 

posted @ 2018-04-09 21:51  LeTri  阅读(111)  评论(0编辑  收藏  举报