魔术球问题

Posted Excim

tags:

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

原题链接:https://www.luogu.org/problemnew/show/P2765

曾经在模拟赛的时候做过弱化版,不需要输出方案。

其实加强版因为数据范围很小,模拟做也未尝不可,暴力算出n=55时最终答案也只有1567,拿二维数组存一下方案即可。

当然,这个题是由网络流做法的,虽然我的网络流做法只是模拟了一个贪心的过程。

贪心的正确性证明就请移步我的弱化版题解好啦:http://www.cnblogs.com/zeroform/p/7115044.html

对于一个球,有两种放法,独占一行或是连到一个柱子上。

把点拆成两个,记为x1与x2,两个点之间不连边,拆成的两个点处理不同情况,为保证合法,向汇点连一条边。

对于独占一行的情况,x1连向源点,

对于连到一个柱子上,其中能和它组成平方数的点y,x2向y1连一条边即可。

设i^i为他们的和。枚举i,i的范围是(sqrt(num),sqrt(num<<1))

显然,i如果<=sqrt(num)的话,那么i*i-num<=0,显然不存在这样的球。

如果 i*i>=num<<1,那么这个球显然要在第num个球之后放下,不在此时的考虑范围之内,由此确定i的范围。

建完边之后,跑最大流即可,isap太难写,于是就用dinic了。

如果第num个点能够放在其他的柱子上的话,那么必然存在这样一个点a,有这样三条边:

 s->a1 a1->num2 num2->t 所以在新图中,最大流不为零。

否则说明所有能与num组成平方数的数字a,上方都已经放了其他的球,图上s->a1这条边一定已经有1的流量流过,此时最大流为0,num必须另找一根柱子放下。

记录每根柱子的开头,dfs时记录它连向的点即可。


 

PS:打dfs的时候少打了一行,而且是最要紧的一行。。。写错之后,不但把建边搞乱了,查方案的时候也没法正常查。

(不知道怎么写《GG记录》,就写到这里好了)

GG记录链接(欣赏一下蒟蒻的zz错误吧):http://www.cnblogs.com/zeroform/p/7678669.html

#include<cstdio>
#include<cmath> 
#include<queue>
#include<cstring>
#include<iostream>
using namespace std;
const int inf=(1<<30);
int n,tot,num,s,t=50003,cnt=1;
struct edge
{
    int u,v,w;
}e[100005];
int l[100005],dis[100005],head[100005];
int nxt[100005],vis[100005];
void add(int u,int v)
{
    e[++cnt].u=head[u];e[cnt].v=v;
    e[cnt].w=1;head[u]=cnt;
    e[++cnt].u=head[v];e[cnt].v=u;
    e[cnt].w=0;head[v]=cnt;
}
int dfs(int x,int f)
{
    if(x==t) return f;
    for(int i=head[x];i!=-1;i=e[i].u)
    {
        int tmp=e[i].v;
        if(dis[tmp]==dis[x]+1&&e[i].w>0)
        {
            int d=dfs(tmp,min(f,e[i].w));
            if(!d) continue;//就是这一行让我debug1.5h
            e[i].w-=d;
            e[i^1].w+=d;
            if(tmp!=t) nxt[x>>1]=(tmp>>1);
            return d;
        }
    }
    return 0;
}
int bfs()
{
    memset(dis,0,sizeof(dis));
    queue<int>q;
    q.push(s);dis[s]=1;
    while(!q.empty())
    {
        int tmp=q.front();q.pop();
        for(int i=head[tmp];i!=-1;i=e[i].u)
        {
            int p=e[i].v;
            if(dis[p]||e[i].w<=0) continue;
            dis[p]=dis[tmp]+1;
            q.push(p);
        }
    }
    return dis[t];
}
int dinic()
{
    int ans=0;
    while(bfs())
    {
        while(1)
        {
            int p=dfs(s,inf);
            if(p==0) break;
            ans+=p;
        }
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    memset(head,-1,sizeof(head));
    while(tot<=n)
    {
        num++;
        add(s,num<<1);
        add(num<<1|1,t);
        for(int i=sqrt(num)+1;i*i<(num<<1);i++)
        {
            add((i*i-num)<<1,num<<1|1);
        }
        if(!dinic()) l[++tot]=num;
    }
    num--;
    printf("%d\\n",num);
    for(int i=1;i<=n;i++)
    {
        if(vis[l[i]]) continue;
        int st=l[i];vis[st]=1;
        while(st)
        {
            printf("%d ",st);
            st=nxt[st];vis[st]=1;
        }
        printf("\\n");
    }
    return 0;
}

 

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

网络流24题- 魔术球问题

网络流24题魔术球问题(最大流)

网络流24题魔术球问题

魔术球问题

魔术球问题

「Luogu2765」[网络流24题] 魔术球问题