sicily 1166. Computer Transformat
题目描述
题目分析
解题过程
心得与收获
目录
一、题目描述
-Constraints
Time Limit: 1 secs, Memory Limit: 32 MB
Description
A sequence consisting of one digit, the number 1 is initially written into a computer. At each successive time step, the computer simultaneously tranforms each digit 0 into the sequence 1 0 and each digit 1 into the sequence 0 1. So, after the first time step, the sequence 0 1 is obtained; after the second, the sequence 1 0 0 1, after the third, the sequence 0 1 1 0 1 0 0 1 and so on.
How many pairs of consequitive zeroes will appear in the sequence after n steps?
Input
Every input line contains one natural number n (0 < n ≤1000).
Output
For each input n print the number of consecutive zeroes pairs that will appear in the sequence after n steps.
Sample Input
2
3
Sample Output
1
1
二、题目分析
大概题意:计算机本身有一个初始输入的值为1,在这个计算机里它有一套转化数的模式,即在一步的操作中会把前一步的数中的0转化为1 0,把1转化为0 1。此题要求的是在n步操作之后形成的数串中,00的对数。用表格分析表示即为
| 步骤数 | 转化结果 | 输出结果a |
|---|---|---|
| 0 | 1 | 0 |
| 1 | 01 | 0 |
| 2 | 1001 | 1 |
| 3 | 01101001 | 1 |
| 4 | 1001011001101001 | 3 |
| 5 | 01101001100101101001011001101001 | 5 |
| 6 | 1001011001101001011010011001011001101001… | 11 |
| 7 | … | 21 |
分析:n>=1,故0时不用考虑。对于这道题,如果想要用模拟的方法来做,那估计这道题是无解了。至少对我们现存的计算机而言2的1000次方是不可数的一个数量级,几乎没有一个编译器和电脑能干这活。因而这道题的思路可由找规律入手。
对于这道题,细心的人可以发现,第k步中00的产生必定由第k-1步的结果的01产生得到,故可以由第k-1步的结果串中看出第k步的结果来,但这貌似没啥卵用。再细心分析,第k-1步01的个数与两个因素有关,一是第k-1步中00的数,即第k-1步的结果,一对0的存在的形式必定为1001的形式,故他有01的结构,在第k步可产生一对0,除此之外,第k-1步中01的结构还和第k-2步的00结果有关,为何这么说?因为第k-2步的00的个数也是以1001的形式存在,可以发现,在1001进行一步的情况下可再生成01101001,除去之前算第k-1步00中含有的01外,他多产生了两个01;故归纳可得(k>=3):
(形式一)
这只是其中一种形式。
根据公式和初始值还可以得到另外一条公式:
(形式二)
此外在做题过程中,由于前两种方法超时让我重新审视了一遍公式二,发现用二进制表示由一定的规律:
| 步骤数k | 结果输出 | 结果2进制表示 |
|---|---|---|
| 1 | 0 | 0 |
| 2 | 1 | 1 |
| 3 | 1 | 1 |
| 4 | 3 | 11 |
| 5 | 5 | 101 |
| 6 | 11 | 1011 |
| 7 | 21 | 10101 |
| 8 | 43 | 101011 |
| …. | … | … |
又可发现(k>1):
(形式三)
分析完毕
三、解题过程
当分析完题目后,可以发现这是比斐波拉契数列还要大的一个数列存在,在1<=n<=1000的要求下,我们必须得用大数的加减,针对于公式一,我的代码如下:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
void temp(int a[],int b[],int n)
{
for(int i=0;i<n;i++)
{
a[i]=b[i];
}
}
void work(int a[],int b[],int n)
{
for(int i=0;i<n;i++)
{
a[i]=a[i]+2*b[i];
}
for(int i=0;i<n;i++)
{
a[i+1]+=a[i]/10;
a[i]%=10;
}
}
int main()
{
long long n;
while(cin >> n)
{
if(n==1)
{
cout << "0\n";
continue;
}
int a[340]={0},b[340]={1},t[340]={0};
for(int i=1;i<n;i++)
{
temp(t,b,1+i/3);
work(b,a,1+i/3);
temp(a,t,1+i/3);
}
int flag=0;
for(int i=(n+1)/3;i>=0;i--)
{
if(a[i]>0) flag=1;
if(flag) printf("%d",a[i]);
}
cout << endl;
}
}
结果愉快的超时了!!!1.02s
再次分析过后打算换个方式,即使用公式2,但这样会有一个问题,得先算出2的幂次方,假如真这样处理,其时间复杂度似乎没有改变。于是在尝试过后有了公式3的解题方法,此时代码为:
#include<iostream>
#include<cstdio>
using namespace std;
void work(int a[],int n)
{
for(int i=0;i<(n+1)/3;i++)
{
a[i]=2*a[i];
}
if(n%2) a[0]++;
else a[0]--;
for(int i=0;i<n/3+1;i++)
{
a[i+1]+=a[i]/10;
a[i]%=10;
}
}
int main()
{
int n;
while(cin >> n)
{
if(n==1)
{
printf("0\n");
continue;
}
int ans[334]={0};
for(int i=1;i<n;i++)
{
for(int j=0;j<(i+1)/3;j++)
{
ans[j]=2*ans[j];
}
if(i%2) ans[0]++;
else ans[0]--;
for(int j=0;j<i/3+1;j++)
{
ans[j+1]+=ans[j]/10;
ans[j]%=10;
}
}
int flag=0;
for(int i=n/3;i>=0;i--)
{
if(!flag&&ans[i]) flag=1;
if(flag) printf("%d",ans[i]);
}
cout << endl;
}
}
结果还是不尽人意的超时了:0.99s
但时间较之前的一种方法有所减少,这让我有了改进此代码的动力和信心,发现了代码中大数进位的循环可以在存值接近饱和时进行,故代码有优化为:
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int n;
while(cin >> n)
{
if(n==1)
{
printf("0\n");
continue;
}
int ans[334]={0};
for(int i=1;i<n;i++)
{
for(int j=0;j<(i+1)/3;j++)
{
ans[j]=2*ans[j];
}
if(i%2) ans[0]++;
else ans[0]--;
if(i%27==0||i==n-1)
for(int j=0;j<i/3+1;j++)
{
ans[j+1]+=ans[j]/10;
ans[j]%=10;
}
}
int flag=0;
for(int i=n/3;i>=0;i--)
{
if(!flag&&ans[i]) flag=1;
if(flag) printf("%d",ans[i]);
}
cout << endl;
}
}
愉快的AC了!!!时间0.92s
我只能说这是为数不多的时间掐的这么接近限制值。。。
因此我相信一定有其他做法能够再优化,或者说有其他更简单方便的做法吧!这个只能待续了。。。
心得与收获
总的来说这道题挺锻炼分析能力的,现在想清楚了回过头来看一点都不难,就是有点绕。说真的从这道题中收获的更多是一种解题的坚持与对数据分析的敏感,再接再厉吧,加油!
探讨了一种特殊的数字序列转换问题,通过分析不同步骤下序列的变化规律,找到高效计算任意步骤后连续00对数量的方法。
883

被折叠的 条评论
为什么被折叠?



