CF1216E Numerical Sequence Hard Version 数学

题目链接 http://codeforces.com/problemset/problem/1216/E2

分析

先考虑它的简化版本,对于这样的一个数列,发现它可以拆分成如下的数列。
\(1\)
\(12\)
\(123\)
\(1234\)
\(12345\)
这样的话我们维护一个前缀和数组,然后去找第\(n\)个数字在哪一行,然后减去前边几行的数字数目和就是答案。
比如\(n=5\),发现它在第三行,前两行的和为\(3\)\(5-3=2\)所以第五个数是\(2\)
但如果\(n\)很大的话,比如这题,就会T掉,首先上述思想是可以肯定的,所以要用更高效的办法
首先是找到在第几行。
一行行枚举效率太低,要用到一个分块的思想,按照末位数字的位数分块,这样在每个块里找,枚举每个块的时间复杂度是一个常数,先找到它在哪一块,然后再利用二分的思想确定所在行,因为这个是具有单调性的,那么怎么找呢?
考虑每一块中有多少个数,用变量\(last\)记录前几个块的数字和,\(len\)记录当前枚举的位数,根据等差数列的求和公式\((a_1+a_n)*n/2\),当前块的第一行的数字和是\(last+len\)手摸一下就能得出,最后一行是\(last+len*cnt\),其中\(cnt\)就是该块内一共有多少数字,如果\(n\)大于前几块的数字和\(sum\),就直接减去,否则就找到了所在块,然后再二分求出是那一行就行,求的方法就是不断更新\(l和r\)判断\(sum\)\(n\)的关系,找到后再减去前边几行的和就是第\(n\)个数字在这一行第几个。
这时候我们再重复一下上述过程,只不过不是拆分行而是拆分每个数(不是数字),比如12345678910,拆成1 2 3 4 5 6 7 8 9 10,1和0不分开的这种。然后再枚举每个数字的位数,依次减去,从而求出第\(n\)个数字所在数的位数,最后再把这个数还原出来取它的那个位数就行。

#include<cstdio>
#define ll long long
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        ll n;
        scanf("%lld",&n);
        ll last=0,cnt,len;
        for(len=1;;len++){
            cnt=len==1?9:cnt*10;
            ll sum=(last+len+last+len*cnt)*cnt/2;
            if(n<=sum)break;
            n-=sum;
            last+=len*cnt;
        }
        ll l=1,r=cnt,ans;
        while(l<=r){
            ll mid=l+r>>1;
            ll sum=(last+len+last+len*mid)*mid/2;
            if(sum>=n){
                ans=mid;
                r=mid-1;
            }else l=mid+1;
        }
        n-=(last+len+last+(ans-1)*len)*(ans-1)/2;
        for(len=1;;len++){
            cnt=len==1?9:cnt*10;
            ll sum=cnt*len;
            if(sum>=n)break;
            n-=sum;
        }
        ll num=(n+len-1)/len;
        n=n-(num-1)*len;
        cnt=1;
        for(int i=1;i<len;i++)cnt*=10;
        num+=cnt-1;
        cnt=len-n+1;
        for(int i=1;i<cnt;i++)num/=10;
        printf("%d\n",num%10);
    }
    return 0;
}
posted @ 2020-04-27 19:12  An_Fly  阅读(145)  评论(0编辑  收藏  举报