魔术球问题

问题描述

    假设有n根柱子,现要按下述规则在这n根柱子中依次放入编号为 1,2,3,...的球。
    (1)每次只能在某根柱子的最上面放球。
    (2)在同一根柱子中,任何2个相邻球的编号之和为完全平方数。
    试设计一个算法,计算出在n根柱子上最多能放多少个球。例如,在4 根柱子上最多可放11个球。

编程任务

    对于给定的n,计算在 n根柱子上最多能放多少个球。

数据输入

    由文件input.txt提供输入数据。文件第1 行有 1个正整数n,表示柱子数。

结果输出

    程序运行结束时,将 n 根柱子上最多能放的球数以及相应的放置方案输出到文件output.txt中。文件的第一行是球数。接下来的n行,每行是一根柱子
上的球的编号。

输入文件示例

input.txt 
4 

输出文件示例

output.txt 
11 
1 8  
2 7 9  
3 6 10  
4 5 11 

题解

第一眼看到这个题,什么想法也没有,但是既然是网络流24题中的一题,那肯定可以用网络流解了,所以强行往网络流上靠。
xjb建图
感觉建的图很不错,非常优美,和解题报告比较一下?
【画面太美,不忍直视
好吧。重构思路,好像可以借鉴最小路径覆盖问题诶?
xjblg
诶?输出方案又错了,
关键时刻要有xp
xp提供的方案是:
正常跑dinic,在程序最后加一个用来判断是否输出的数组,从1开始循环,遇到一个没有被输出的数就输出,然后顺着这个点沿着流量已经用尽的边往下跑,直到没有可以到达的点为止。

见图:
这里写图片描述

首先我们发现1是没有被输出过的,于是沿着1的边往下跑,到达3,输出,发现3仍然可以跑到4,就输出4。在跑的过程中,不断给这些点打上输出标记。
又发现2没有被输出过,但是它的两条边的remain值都存在,也就是在这个最大流网络中,没有从2流出的流量,于是只能输出2。程序结束。

貌似还没有讲网络流的主体。
我们可以每次加入一个点,也就是增加一个小球,然后枚举每一个小于它的编号的数,如果可以加成完全平方数,我们就从这个较小的数连一条到达这个点的边,流量为1。当我们发现当前的点数减去最大流的值,也就是所需的柱子数已经比输入要求的大了(一定是大1,这对于理解输出很有帮助),停止加点。
枚举1到现在的点数减1,也就是能放的最多的小球数,进行上面说的输出操作。
有人可能会问:现在不是已经多一个点了么?这样输出不会将多加的那个点输出么?
其实是不会出现这种情况的。因为既然多加了这一个点,所需柱子数就加一,那么这个新的点一定是独自占了一个柱子,所以只要我们在输出的时候不枚举到它就可以了。

CODE:

#include<cstdio>
#include<cstring>
const int INF=1e9;
struct queue
{
    int h,t;
    int a[10001];
    inline void clear(){h=1,t=0;}
    inline void push(int n){a[++t]=n;}
    inline int front(){return a[h];}
    inline void pop(){h++;}
    inline bool empty(){return h>t;}
}q;
struct edge
{
    int next,to,remain;
}a[50000];
int head[5000];
int deep[5000];
int number[]={0,1,4,9,16,25,36,49,64,81,100,121,144,169,196,225,256,289,324,361,400,441,484,529,576,625,676,729,784,841,900,961,1024,1089,1156,1225,1296,1369,1444,1521,1600,1681,1764,1849,1936,2025,2116,2209,2304,2401,2500,2601,2704,2809,2916,3025,3136,3249,3364,3481,3600,3721,3844,3969,4096};
bool b[5000];
bool p[5000];
int n,g,ans,num=1,tmp,S,T;
inline int min(int a,int b){return a<b?a:b;}
inline void add(int x,int y,int cap)
{
    a[++num].next=head[x],a[num].to=y,a[num].remain=cap,head[x]=num;
    a[++num].next=head[y],a[num].to=x,head[y]=num;
}
inline bool bfs()
{
    memset(deep,0x3f,sizeof(deep));
    q.clear();q.push(S);
    deep[S]=0;
    while(!q.empty())
    {
        int tmp=q.front();q.pop();
        for(int i=head[tmp];i;i=a[i].next)
          if(deep[a[i].to]>INF&&a[i].remain)
            q.push(a[i].to),deep[a[i].to]=deep[tmp]+1;
    }
    return deep[T]<INF;
}
int dfs(int now,int limit)
{
    if(now==T||!limit) return limit;
    int flow=0,f;
    for(int i=head[now];i;i=a[i].next)
      if(deep[a[i].to]==deep[now]+1&&a[i].remain&&(f=dfs(a[i].to,min(limit,a[i].remain))))
      {
        flow+=f,limit-=f,a[i].remain-=f,a[i^1].remain+=f;
        if(!limit) return flow;
      }
    deep[now]=-1;
    return flow;
}
inline int dinic()
{
    int ans=0;
    while(bfs()) ans+=dfs(S,INF);
    return ans;
}
inline void print(int now)
{
    printf("%d ",now);
    p[now]=1;
    int i=head[now];
    while(i)
      if(a[i].to!=S&&a[i].to!=T&&!a[i].remain)
      {
        printf("%d ",a[i].to-2000);
        p[a[i].to-2000]=1;
        i=head[a[i].to-2000];
      }
      else i=a[i].next;
    printf("\n");
}
int main()
{
    scanf("%d",&n);
    S=4900;T=4901;
    for(int i=1;i<=64;i++)
      b[number[i]]=1;
    while(1)
    {
        g++;
        add(S,g,1);
        add(g+2000,T,1);
        for(int i=1;i<g;i++)
          if(b[g+i]) add(i,g+2000,1);
        tmp+=dinic();
        if(g-tmp==n) ans=g;
        else if(g-tmp==n+1)
        {
            printf("%d\n",ans);
            for(int i=1;i<=g-1;i++)
              if(!p[i]) print(i);
            return 0;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值