首页 > 代码库 > ZOJ2599:Graduated Lexicographical Ordering(非常经典的数位DP)
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\n",pre); } return 0; }