sicily 1166. Computer Transformat

探讨了一种特殊的数字序列转换问题,通过分析不同步骤下序列的变化规律,找到高效计算任意步骤后连续00对数量的方法。

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
010
1010
210011
3011010011
410010110011010013
5011010011001011010010110011010015
61001011001101001011010011001011001101001…11
721

    

        分析: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进制表示
100
211
311
4311
55101
6111011
72110101
843101011
….

    
又可发现(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
      我只能说这是为数不多的时间掐的这么接近限制值。。。
因此我相信一定有其他做法能够再优化,或者说有其他更简单方便的做法吧!这个只能待续了。。。

心得与收获

        总的来说这道题挺锻炼分析能力的,现在想清楚了回过头来看一点都不难,就是有点绕。说真的从这道题中收获的更多是一种解题的坚持与对数据分析的敏感,再接再厉吧,加油!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值