数论问题——答案集有限的情况下一种想法


2014.09.09:我现在才知道这就是打表……会打表的可以不用看了……

考虑这个问题:

Input

输入一个自然数n (1<=n<=1000),请判断是否存在2*n+1个连续的自然数满足左边n+1个数的平方和等于右边n个数的平方和。

Output

如果你找到这样的解,输出这2*n+1个数,每两个数之间一个空格。



请别先吐槽!!我知道有一个公式可以很方便地解决这个问题,毕竟那才是这道题的标准解法。现在考虑一下,假设我们不知道这个公式,一个很自然的想法是根据题意暴力枚举:

#include <iostream>
using namespace std;

typedef unsigned long long ullong;

int main()
{
  int n=0;
  while(cin>>n)
  {
    for(int i=1;i<=1000;++i)
    {
      ullong s1=0,s2=0;
      for(int j=0;j<n+1;++j)
      {
        s1+=(i+j)*(i+j);
      }
      for(int j=n+1;j<n+n+1;++j)
        s2+=(i+j)*(i+j);
      if(s1==s2)
      {
        for(int j=0;j<n+n;++j)
          cout<<i+j<<" ";
        cout<<i+n+n<<endl;
        break;
      }
    }
  }
  return 0;
}

毋庸置疑,这段代码提交上去肯定超时,即使把cin、cout换成scanf()和printf()也无济于事。考虑到数论问题的解一般都有一定的规律,我们
先用之前的程序输出n=1~10时的情况看看:

n=1
3 4 5
n=2
10 11 12 13 14
n=3
21 22 23 24 25 26 27
n=4
36 37 38 39 40 41 42 43 44
n=5
55 56 57 58 59 60 61 62 63 64 65
n=6
78 79 80 81 82 83 84 85 86 87 88 89 90
n=7
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
n=8
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
n=9
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
n=10
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230

………………除了n越大,输出的数越大,有谁从这里面看出其他规律来了么?有的话请务必告诉我。

当然,这种递增性还是可以用一下的,if you want:

#include <iostream>
using namespace std;

typedef unsigned long long ullong;

int main()
{
  int n=0;
  while(cin>>n)
  {
    ullong t=0;
    if(n>=100) t=20100;
    if(n>=200) t=80200;
    if(n>=250) t=125250;
    if(n>=300) t=180300;
    if(n>=350) t=245350;
    if(n>=400) t=320400;
    if(n>=450) t=405450;
    if(n>=500) t=500500;
    if(n>=550) t=605550;
    if(n>=575) t=661825;
    if(n>=600) t=720600;
    if(n>=650) t=845650;
    if(n>=675) t=911925;
    if(n>=700) t=980700;
    if(n>=750) t=1125750;
    if(n>=775) t=1202025;
    if(n>=800) t=1280800;
    if(n>=850) t=1445850;
    if(n>=875) t=1532125;
    if(n>=900) t=1620900;
    if(n>=950) t=1805950;
    if(n>=960) t=1844160;
    if(n>=980) t=1921780;
    if(n>=1000) t=2001000;
    for(ullong i=t;;++i)
    {
      ullong s1=0,s2=0;
      for(ullong j=0;j<n+1;++j)
      {
        s1+=(i+j)*(i+j);
      }
      for(ullong j=n+1;j<n+n+1;++j)
        s2+=(i+j)*(i+j);
      if(s1==s2)
      {
        for(ullong j=0;j<n+n;++j)
          cout<<i+j<<" ";
        cout<<i+n+n<<endl;
        break;
      }
    }
  }
  return 0;
}


提交上去,恭喜你, Time Limit Exceeded

嘛,本来在答案集有限的情况下可以在接受输入之前把答案找出来然后扔进一个数组中,像这样:

#include <iostream>
#include <cstring>
using namespace std;

typedef unsigned long long ullong;
ullong tmp[1001];
void solution()
{
  tmp[1]=3;
  int n=2;
  for(ullong i=tmp[n-1];;++i)
  {
    ullong s1=0,s2=0;
    for(ullong j=0;j<n+1;++j)
    {
      s1+=(i+j)*(i+j);
    }
    for(ullong j=n+1;j<n+n+1;++j)
      s2+=(i+j)*(i+j);
    if(s1==s2)
    {
      tmp[n]=i;
      break;
    }
  }
}
int main()
{
  solution();
  int n=0;
  while(cin>>n)
  {
    for(ullong j=0;j<n+n;++j)
      cout<<tmp[n]+j<<" ";
    cout<<tmp[n]+n+n<<endl;
  }
  return 0;
}

嗯,交上去之前现在本地测试下如何?然后你会发现程序貌似不接受你的输入,取决于机器配置,您的程序可能在若干秒开始接受输入,在我的机器上,大约是25s。嗯,每输一个n,程序都会极快地输出结果,看起来应该不会超时了……才怪!!那之前还有25s啊!!

那,现在该怎么办呢?刚才的程序,如果把那25s用于计算结果的时间给剔掉的话,问题就能解决了……剔掉25s……25s用于计算……嗯?

25s是用于计算的,那只要提前把计算结果写在程序中,那不就可以省下这25秒了么?

当然,1000个数,不太可能手写(等你写完天都黑了)。既然不能手写,那就写个辅助程序输出吧:

#include <iostream>
using namespace std;

typedef unsigned long long ullong;
int main()
{
  cout<<"{";
  ullong i=3;
  for(int n=1;n<=1000;++n)
  {
    for(;;++i)
    {
      ullong s1=0,s2=0;
      for(ullong j=0;j<n+1;++j)
      {
        s1+=(i+j)*(i+j);
      }
      for(ullong j=n+1;j<n+n+1;++j)
        s2+=(i+j)*(i+j);
      if(s1==s2)
      {
        cout<<i;
        if(n!=1000) cout<<",";
        break;
      }
    }
  }
  cout<<"};";
  return 0;
}

编译后,打开命令行,将上述程序输出重定向到一个文件:

    xxx.exe > 1.txt

等待程序执行完成后,打开生成的1.txt,将里面的内容Ctrl+A Ctrl+C复制到代码中给数组初始化:

#include <iostream>
using namespace std;

typedef unsigned long long ullong;
ullong tmp[1001]={3,10,21,36,/*(太多省略)*/,2001000};

int main()
{

  int n=0;
  while(cin>>n)
  {
    for(ullong j=0;j<n+n;++j)
      cout<<tmp[n]+j<<" ";
    cout<<tmp[n]+n+n<<endl;
  }
  return 0;
}
测试,没问题;交上去, Accepted!!

哈哈,成功了!!

总结

在我兴奋过头迫不及待地把这个消息告诉给一位同样搞ACM的朋友时,他十分冷静地指出这种方法“用途十分有限”,不过当时我正在兴头上,没怎么在意。之后冷静下来想了想,发现这方法确实有蛮多的问题。

第一个问题是,我不知道正式比赛允不允许这样做,毕竟我只是个新手,没参加过正式的ACM竞赛。

第二,跟输入前计算一样,此法一样要求输入集有限并且较小,输出同样能装入内存。

第三,结果必须在可接受的时间内取得,这次是25s,还可接受,错了也还有时间改,要是算一次就要半个小时,还是想想其他办法吧。

第四,这种方法并没解决实际问题,综合来考虑,算法并没有得到优化,自己的能力也没有得到提升,只能是纯粹为了竞赛。如果频繁使用此法,对自己解题能力的提升并没有好处。

因为上述原因,我觉得即使正式比赛允许,这种方法也不要放在前面考虑,如果凭自己的能力用正统方法实在做不出来,那用这种方法过一两道,倒也无大碍。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值