• 题目分享C 二代目


    题意:一个数列是由 1 1 2 1 2 3 1 2 3 4 1 2 3 4 5 1 2 3 4 5 6.。。。。组成,也就是1-1,1-2,1-3......并且如果遇到多位数也要拆成数字比如1-10就是1 2 3 4 5 6 7 8 9 1 0共11个数字,问第K位是几?

    分析:这可能是分享题里比较考验代码能力的题了,

    这题唯一运用的算法可能就是二分吧

    大体过程也非常好想

    就是先确定离他最近且小于他的1-n是几,再算出来他是1-n+1中的答案

    比如我要找11 那么离他最近且小于他的 1-n这个n就是4 因为1 1 2 1 2 3 1 2 3 4,此时数列已经10个数了,如果再补个1-5就超过11了

    然后显然只需要在1-5中找第11-10个数字是多少就行了,得到答案1

    找最近的1-n是几首先你要能算出来对于给定的n,能求出1-n有多少位,再用二分枚举出这个n是几,

    二分大家肯定都能想到,问题是怎么计算1-n有多少位

    这里我定义sum 1-n是指形如1 1 2 1 2 3 1 2 3 4.。。。1 2。。。 n这种的数字数

    num 1-n是指形如1 2 3 。。。。n这种的数字数

    先想如何求出num来,

    很容易想到,这就是个简单的估计都不算数位dp的数位dp

    位数有i位的数显然共有i*(10^i-10^i-1)个数字,因为你长达i位的数显然有10^i-10^i-1个,比如2位的数有10,11,。。。。,99共100-10=90个数

    每个数字都有i位,很容易得到这个结论

    那么任意的n,num也就很好求了

    只需要把比他位数小的求个和,(设n有i位)再加上(n-(10^i-1)+1)*i,这个也很容易推出来,和上面几乎一样

    那么求num 的代码也就很好写

    ll chuli_num(int x)
    {
        int i;
        ll ans=0ll;
        for(i=1;i<=9;i++)
        {
            if(pow_10[i]>x) break;
            ans+=(ll)(pow_10[i]-pow_10[i-1])*i;
        }
        ans+=(ll)(x-pow_10[i-1]+1)*i;
        return ans;
    }

    然后就是求sum

    考虑和求num的过程类似,只不过式子不太一样

    在求i位的数的sum值时,可以想一下他长什么样,显然是1-10 1-11 1-12 1-13 ......1-99

    直接遍历一遍然后调用求num的函数显然会t

    不难发现的是1-11比1-10只多了一个11,也就是两位,所以其实这是一个等差数列求和

    首项a1就是1-10^i-1的num值,公差d就是位数i,项数n还是10^i-10^i-1

    sum=n*a1+n*(n-1)/2*d

    当然任意n值也是一样用等差数列求和, 只不过项数变了而已

    代码也能写出来了

    ll chuli_sum(int x)
    {
        int i;
        ll ans=0ll;
        for(i=1;i<=9;i++)
        {
            if(pow_10[i]>x) break;
            int n=pow_10[i]-pow_10[i-1];
            int a1=chuli_num(pow_10[i-1]);
            ans+=(ll)n*a1+(ll)n*(n-1)/2*i;
        }
        int n=x-pow_10[i-1]+1;
        int a1=chuli_num(pow_10[i-1]);
        ans+=(ll)n*a1+(ll)n*(n-1)/2*i;
        return ans;
    }

    插句嘴,这题k是1e18,显然最大的n数,怎么也不可能超过1e9,因为光每个数都只算1位都有n*(n+1)/2个数,这已经与1e18很接近了

    而就是因为这个将近1e9的数,原本一个只需要递推求num与sum的方法数组开不下,时间也不允许,

    for(int i=1;i<=9;i++) for(int j=pow_10[i-1];j<pow_10[i];j++) a[j]=a[j-1]+i,b[j]=b[j-1]+a[j];

    这里a就是num数组,b就是sum数组,这只有一行的递推代码显然要比上面的要好写数倍

    接下来就是二分的过程了,

    首先显然是从sum里找不超过k的最大的sum了

    l等于0就行,r取1e9就行,这个二分显然最后结束是要的r的值,这玩意不用再讲了吧。。

    l=0,r=999999999;
    while(l<=r)
    {
        mid=l+r>>1;
        now=chuli_sum(mid);
        if(now>k) r=mid-1;
        else if(now==k)
        {
            r=mid;
            break;
        }
        else l=mid+1;
    }

    然后k要减去r对于的sum值,再对num进行一次二分

    这次的r’显然不会超过r,因为这次的数字本应就只能取1-r+1,而取r+1显然会被归到二分结果是r+1的那种情况里,所以显然最终结果不会超过r

    这里我们依然是找小于等于k的最大的num值,

    而k与这个值的差就是我们选的下一个数字的第几位

    比如现在剩下k还剩2位,下一个数是123456

    那么我们要的就是2

    这里怎么处理都可以

    我是先算出123456的位数,再抹掉3456,最后mod10,

    这里用啥方法都行,

    能取出2来就行

    int i;
    for(i=1;i<=9;i++) if(pow_10[i]>r+1) break;
    printf("%d
    ",(r+1)/pow_10[i-k]%10);

    最后的话,这几块代码中间还有一点点细节,也很容易看懂

    比如,在第一次sum后剩余的k如果是0的话,就要输出r的num值的最后一位(显然)

    再或者,在第二次sum后剩余的k如果是0的话,就要输出r的值的最后一位(显然++)

    这些分开写可能都挺好写的,但合在一起任何一个环节出错就没分了,所以挺考验代码能力的

    代码:

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    
    #define ll long long
    
    int pow_10[10]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
    
    ll chuli_num(int x)
    {
        int i;
        ll ans=0ll;
        for(i=1;i<=9;i++)
        {
            if(pow_10[i]>x) break;
            ans+=(ll)(pow_10[i]-pow_10[i-1])*i;
        }
        ans+=(ll)(x-pow_10[i-1]+1)*i;
        return ans;
    }
    
    ll chuli_sum(int x)
    {
        int i;
        ll ans=0ll;
        for(i=1;i<=9;i++)
        {
            if(pow_10[i]>x) break;
            int n=pow_10[i]-pow_10[i-1];
            int a1=chuli_num(pow_10[i-1]);
            ans+=(ll)n*a1+(ll)n*(n-1)/2*i;
        }
        int n=x-pow_10[i-1]+1;
        int a1=chuli_num(pow_10[i-1]);
        ans+=(ll)n*a1+(ll)n*(n-1)/2*i;
        return ans;
    }
    
    int main()
    {
        int q,l,r,mid;
        ll k,now;
        scanf("%d",&q);
        while(q--)
        {
            scanf("%lld",&k);
            l=0,r=999999999;
            while(l<=r)
            {
                mid=l+r>>1;
                now=chuli_sum(mid);
                if(now>k) r=mid-1;
                else if(now==k)
                {
                    r=mid;
                    break;
                }
                else l=mid+1;
            }
            k-=chuli_sum(r);
            if(!k)
            {
                printf("%d
    ",r%10);
                continue;
            }
            l=0;
            while(l<=r)
            {
                mid=l+r>>1;
                now=chuli_num(mid);
                if(now>k) r=mid-1;
                else if(now==k)
                {
                    r=mid;
                    break;
                }
                else l=mid+1;
            }
            k-=chuli_num(r);
            if(!k)
            {
                printf("%d
    ",r%10);
                continue;
            }
            int i;
            for(i=1;i<=9;i++) if(pow_10[i]>r+1) break;
            printf("%d
    ",(r+1)/pow_10[i-k]%10);
        }
        return 0;
    }
  • 相关阅读:
    Maximal Square
    Count Complete Tree Nodes
    Rectangle Area
    Implement Stack using Queues
    Basic Calculator
    Invert Binary Tree
    Summary Ranges
    Basic Calculator II
    Majority Element II
    Kth Smallest Element in a BST
  • 原文地址:https://www.cnblogs.com/lin4xu/p/12784086.html
Copyright © 2020-2023  润新知