zoukankan      html  css  js  c++  java
  • ZOJ2599:Graduated Lexicographical Ordering(很经典的数位DP)

    Consider integer numbers from 1 to n. Let us call the sum of digits of an integer number its weight. Denote the weight of the number x as w(x).

    Now let us order the numbers using so called graduated lexicographical ordering, or shorter grlex ordering. Consider two integer numbers a and b. If w(a) < w(b) then a goes before b in grlex ordering. If w(a) = w(b) then a goes before b in grlex ordering if and only if the decimal representation of a is lexicographically smaller than the decimal representation of b.

    Let us consider some examples.

    • 120 < grlex4 since w(120) = 1 + 2 + 0 = 3 < 4 = w(4).
    • 555 < grlex78 since w(555) = 15 = w(78) and "555" is lexicographicaly smaller than "78".
    • 20 < grlex200 since w(20) = 2 = w(200) and "20" is lexicographicaly smaller than "200".

    Given n and some integer number k, find the position of the number k in grlex ordering of integer numbers from 1 to n, and the k-th number in this ordering.


    Input

    There are several lines in the input file, and each line stands two integers n and k (1 <= k <= n <= 1018). A line with n = k = 0 ends up the input.


    Output

    For each line in the input, output one line in the output file. First print the position of the number k in grlex ordering of integer numbers from 1 to n, and then the integer that occupies the k-th position in this ordering.


    Sample Input

    20 10
    0 0
    

    Sample Output

    2 14
    
    

    题意:先把1~n内的数依照数位和排一次序。然后数位和相等的依照字典序拍一次序,然后输出k的位置和第k个位置的数

    这道题在高逸涵的论文《数位计数问题解法研究》中有
    他给出了5个函数

    1. getSum1(int L, int sum); 数字和为 sum 的 L 位数字的个数(以0为前缀的也算数)

    2. getSum2(LL n, int sum); 返回 1~n 中数字和为 sum 的数的个数

    3. getSum3(LL n, LL prefix, int sum); 返回 1~n 中数字和为 sum 前缀为 prefix 的数的个数

    4. getSum4(LL n, LL k, int sum); 返回 1~n 中数字和为 sum 且字典序小于k的数的个数

    5. getSum5(LL n, LL k); 返回 k 在 1~n 中的位置

    用这五个函数解决这道题
    在ZOJ提交后吃惊的发现自己排在d第2,并且与第1的执行时间和内存是一样的,仅仅是他是08年提交的。我是14年提交的
    
    
    
    
    #include <stdio.h>
    #include <string.h>
    #include <algorithm>
    using namespace std;
    #define ll __int64
    #define up(i,x,y) for(i=x;i<=y;i++)
    #define down(i,x,y) for(i=x;i>=y;i--)
    #define mem(a,b) memset(a,b,sizeof(a))
    #define w(x) while(x)
    ll n,k;
    ll dp[20][200];
    //数字和为 sum 的 L 位数字的个数(以0为前缀的也算数)
    ll  getSum1(int L, int sum)
    {
        if(sum>9*L || sum<0 || L<0)
            return 0;
        if(dp[L][sum])
            return dp[L][sum];
        if(!L&&!sum)
            return 1;
        int i;
        up(i,0,9)
        {
            if(sum-i<0)break;
            dp[L][sum]+=getSum1(L-1,sum-i);
        }
        return dp[L][sum];
    }
    // 返回 1~n 中数字和为 sum 的数的个数
    ll getSum2(ll n, int sum)
    {
        int bit[20],i,len=0,j;
        ll ans=0;
        w(n)
        {
            bit[len++]=n%10;
            n/=10;
        }
        down(i,len-1,0)
        {
            up(j,0,bit[i]-1)
            ans+=getSum1(i,sum--);
        }
        ans+=getSum1(0,sum);
        return ans;
    }
    //返回 1~n 中数字和为 sum 前缀为 prefix 的数的个数
    ll getSum3(ll n,ll prefix,int sum)
    {
        char sn[20],sp[20];
        int ln,lp,i,j;
        ll ans = 0;
        sprintf(sn,"%I64d",n);//将数字转化为字符串
        sprintf(sp,"%I64d",prefix);
        ln=strlen(sn),lp=strlen(sp);
        up(i,0,lp-1)
        sum-=sp[i]-'0';
        up(i,0,lp-1)
        if(sn[i]!=sp[i])
            break;
        if(i<lp)
        {
            if(sn[i]<sp[i]) ln--;//假设前缀的这一位大于n。那么从第二高位匹配,看作n的长度减去1
            down(i,ln-lp,0)
            ans+=getSum1(i,sum);
            return ans;
        }
        ll tem=0;
        up(i,lp,ln-1)
        tem=tem*10+sn[i]-'0';
        ans+=getSum2(tem,sum);
        down(i,ln-lp-1,0)
        ans+=getSum1(i,sum);
        return ans;
    }
    //返回 1~n 中数字和为 sum 且字典序小于k的数的个数
    ll getSum4(ll n,ll k,int sum)
    {
        int bit[20],i,len=0,j,t=1;
        ll ans=0,pre=1;
        w(k)
        {
            bit[len++]=k%10;
            k/=10;
        }
        down(i,len-1,0)//枚举前缀
        {
            up(j,t,bit[i]-1)
            {
                ans+=getSum3(n,pre++,sum);
            }
            pre*=10;
            t = 0;
        }
        //比如1000。小于k的有1,10,100这些是在前面统计过程中不涉及的
        up(i,0,len-1)
        {
            if(bit[i]==0)ans++;
            else break;
        }
        return ans;
    }
    //返回 k 在 1~n 中的位置
    ll getSum5(ll n,ll k)
    {
        ll sum = 0,tem=k,ans=0,i;
        while(tem)
        {
            sum+=(tem%10);
            tem/=10;
        }
        up(i,1,sum-1)//计算全部和小于sum的个数
        ans+=getSum2(n,i);
        ans+=getSum4(n,k,sum);//和等于sum,可是字典序小于k的个数
        return ans+1;//位于那些数的后一个位置
    }
    int main()
    {
        mem(dp,0);
        w((scanf("%I64d%I64d",&n,&k),n+k))
        {
            printf("%I64d ",getSum5(n,k));
            ll pre=1,presum=1,sum=1,t;
            w(((t=getSum2(n,sum))<k))//统计数位和。从1開始,确定第k个位置的数位和
            {
                sum++;
                k-=t;
            }
            w(1)
            {
                w(((t=getSum3(n,pre,sum))<k))//确定k位置的数位和后,再比較前缀,找出k位置的数
                {
                    pre++;
                    presum++;
                    k-=t;
                }
                if(presum==sum) break;//数位和确定
                pre*=10;
            }
            w(--k)pre*=10;//由于数位和确定,且前缀同样的情况还有可能是1,10,100这些类型,也要考虑
            printf("%I64d
    ",pre);
        }
    
        return 0;
    }
    


    
       
    
  • 相关阅读:
    vue源码中的检测方法
    Node.js中Streams
    5_PHP数组_3_数组处理函数及其应用_6_数组检索函数
    5_PHP数组_3_数组处理函数及其应用_8_数组与数据结构
    5_PHP数组_3_数组处理函数及其应用_7_数组排列函数
    5_PHP数组_3_数组处理函数及其应用_1_快速创建数组的函数
    5_PHP数组_3_数组处理函数及其应用_2_数组统计函数
    5_PHP数组_3_数组处理函数及其应用_3_数组指针函数
    5_PHP数组_3_数组处理函数及其应用_4_数组和变量间的转换函数
    4_PHP流程控制语句_3_程序跳转和终止语句
  • 原文地址:https://www.cnblogs.com/gavanwanggw/p/7137324.html
Copyright © 2011-2022 走看看