BZOJ2656: [Zjoi2012]数列(sequence)

【题目描述】
小白和小蓝在一起上数学课,下课后老师留了一道作业,求下面这个数列的通项公式:
A_0=0
A_1=1
A_2i=A_i(for any integer i>0)
A_2i+1=A_i+A_i+1(for any integer i>0)

小白作为一个数学爱好者,很快就计算出了这个数列的通项公式。于是,小白告诉小蓝自己已经做出来了,但为了防止小蓝抄作业,小白并不想把公式公布出来。于是小白为了向小蓝证明自己的确做出来了此题以达到其炫耀的目的,想出了一个绝妙的方法:即让小蓝说一个正整数N,小白则说出 的值,如果当N很大时小白仍能很快的说出正确答案,这就说明小白的确得到了公式。但这个方法有一个很大的漏洞:小蓝自己不会做,没法验证小白的答案是否正确。作为小蓝的好友,你能帮帮小蓝吗?
【输入说明】
输入文件第一行有且只有一个正整数T,表示测试数据的组数。
第2~T+1行,每行一个非负整数N。
【输出说明】
输出文件共包含T行。
第i行应包含一个不含多余前缀0的数,它的值应等于 (n为输入数据中第i+1行被读入的整数)
【样例输入】
3
1
3
10
【样例输出】
1
2
3

【数据范围】
对于20%的数据,N<=10^8
对于50%的数据,N<=10^13
对于100%的数据,T<=20,N<=10^100

【分析】
真·良心题!
打个表三分钟不到看出规律~

根据这个递推公式的形式,我们不难想到对于各项关于2的幂来分段。
这里是N<=32时A_i的值

注意到一个非常机智的事情,虽然2^k同时出现在两行这是不科学的,但是有没有发现这样让每行都对称了,好高贵有木有= =

接着上面的无聊,稍稍调整,我们发现了一个更惊人的事实:
这里写图片描述
这种规律并不是偶然,事实上这是因为A_2i=A_i,由于本蒟蒻数学渣渣,就不写一大堆推导过程和高贵的证明了。

但是这个有什么用呢?YY一下就可以发现:
A_24=A_16+A_32
A_20=A_16+A_24;A_28=A_24+A_32
……

有没有一丝二分查找的味道?
所以,对于一个N我们将它二进制展开,N=a_k*2^k+a_k-1*2^k-1…a_1*2^1。
然后用类似二分的方法递归求解。
先约定L=1,R=1,L和R表示的是A_l和A_r值,Mid=L+R;
举19为例,19的二进制形式为10011。
第一项1去掉,因为它表示的是19在我们三角形的第4+1行。
第二项为0,即19<2^4+2^3,很明显19应当在第5行的前1/2部分,所以应当有R=Mid,此时L=1,R=2。
以此类推:
第三项为0,19应当在第5行的[0,1\4],此时L=1,R=3。
第四项为1,19应当在第5行[1\8,1\4],此时L=4,R=3。
第五项为1,19应当在第5行[3\16,1\4],此时L=7,l指向19。
输出L,算法结束。

代码:

/**************************************************************
    Problem: 2656
    User: Array98
    Language: C++
    Result: Accepted
    Time:44 ms
    Memory:1288 kb
****************************************************************/

#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
vector <long long> Two;
int N[1010],L[1010],R[1010],Mid[1010];
string S;

inline void copy(int *A, const int B[]) {for (int i=0; i<=B[0]; i++) A[i]=B[i];}

void Divide()
{
    for (int i=N[0]; i>=1; i--)
    {
        if (N[i]%2 && i!=1) N[i-1]+=10; N[i]/=2;
    }

    while (N[N[0]]==0 && N[0]) N[0]--;
}

void Add(int A[], int B[], int *C)
{
    C[0]=max(A[0],B[0]);
    for (int i=1; i<=C[0]; i++)
    {
        C[i]+=A[i]+B[i];
        if (C[i]>9) C[i]%=10,C[i+1]++;
    }
    if (C[C[0]+1]>0) C[0]++;
}

void Clear()
{
    memset(L,0,sizeof(L)); memset(R,0,sizeof(R)); memset(N,0,sizeof(N));
    L[0]=L[1]=R[0]=R[1]=1;
    Two.clear();
}

int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        Clear();
        cin >> S; N[0]=S.size(); reverse(S.begin(),S.end()); 
        for (int i=1; i<=N[0]; i++) N[i]=S[i-1]-'0';
        while (N[0]) Two.push_back(N[1]%2),Divide();
        Two.pop_back(); reverse(Two.begin(),Two.end());
        for (int i=0; i<Two.size(); i++)
        {
            memset(Mid,0,sizeof(Mid));
            Add(L,R,Mid);
            if (Two[i]) copy(L,Mid); else copy(R,Mid);
        }
        for (int i=L[0]; i>=1; i--) printf("%d",L[i]); 
        printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值