ZOJ 2599 Graduated Lexicographical Ordering ★(数位DP)

题意

定义两个数的比较方法,各位数字之和大的数大,如果数字和相等则按字典序比较两个数的大小。输入n,k,求:1.数字k的排名;2.排名为k的数。

思路

算是一类经典的统计问题的拓展吧~ 先来看第一问。 求数字K的排名,变相得看就是求[1,N]中小于K的数的个数,数位DP统计下即可(记忆化搜索方式,dfs()过程):pos表示处理的位置;dig_sum表示当前枚举的数位和;隐藏的全局比较对象k_sum是K的数位和;start表示枚举数位开始的位置,即第一个非零位,便于按位DP时比较字典序;flag表示字典序是否小于K,-1表示小于,0表示未决定,1表示大于,比较过程在按位DP过程中即完成(详见next_flag计算);limit表示当前数位的数字是否达上限。 然后来看第二问。如果是像此题这样和数大小不成线性关系的排名规则,尝试正着求排名可能会比较麻烦。所以 一般处理这种排序问题的方法是用某种方法枚举符合规则的数,然后判断这个数是不是排第K(求一个数的排名用第一问的方法)。一般枚举数可以二分,blablabla……当然这道题二分不行,因为字典序和数位和规则都不具备单调性。而朴素枚举显然不行,在这道题中,大家普遍的方法是枚举前缀~具体一点儿就是:枚举第一位是几,再枚举第二位是几,直到确定这个数字。枚举每一位时都是从0~9(除了第一位不能是0),这样就保证了枚举的顺序符合字典序递增的顺序。而我们也可以通过枚举数位和来算出第K个数的数位和(见代码)。总结一下第二问的思路就是:先算出第K个数的数位和k_dig_num,然后枚举前缀,根据当前前缀、数位和为k_dig_num下的数的个数,由于字典序,所以前面的数一定小于第K个数,这样下去知道当前枚举到的数是第K个数。

代码

  [cpp] #include <iostream> #include <cstdio> #include <cmath> #include <algorithm> #include <string> #include <cstring> #include <vector> #include <set> #include <stack> #include <queue> #define MID(x,y) ((x+y)/2) #define MEM(a,b) memset(a,b,sizeof(a)) #define REP(i, begin, end) for (int i = begin; i <= end; i ++) using namespace std; typedef unsigned long long LL; typedef vector <int> VI; int k_sum, k_dig_sum; VI num, k_num; LL dp[25][200][25][5], fsum[25][200]; LL res1, res2; int sum; LL dfs(int pos, int dig_sum, int start, int flag, bool limit){ if (pos == -1){ if (dig_sum == 0) return 0; if (dig_sum > k_sum) return 0; else if (dig_sum == k_sum){ if (!flag) if ((int)k_num.size()-1-(start-pos) >= 0) return 1; else return 0; else{ return (flag==-1)?1:0; } } else if (dig_sum < k_sum) return 1; } if (!limit && ~dp[pos][dig_sum][start][flag]) return dp[pos][dig_sum][start][flag]; int end = limit?num[pos]:9; LL res = 0; REP(i, 0, end){ bool st = (start == pos && i == 0); int next_flag; if (flag != 0) next_flag = flag; else{ if (st) next_flag = 0; else { if ((int)k_num.size()-1-(start-pos) < 0) next_flag = 1; else{ if (i < k_num[(int)k_num.size()-1-(start-pos)]) next_flag = -1; else if (i == k_num[(int)k_num.size()-1-(start-pos)]) next_flag = 0; else next_flag = 1; } } } res += dfs(pos-1, dig_sum+i, st?start-1:start, next_flag, limit&&(i==end)); } return limit?res:dp[pos][dig_sum][start][flag]=res; } LL find_dig_sum(int pos, int dig_sum, bool limit){ if (pos == -1) return dig_sum == sum; if (!limit && ~fsum[pos][dig_sum]) return fsum[pos][dig_sum]; int end = limit?num[pos]:9; LL res = 0; REP(i, 0, end){ res += find_dig_sum(pos-1, dig_sum+i, limit&&(i==end)); } return limit?res:fsum[pos][dig_sum]=res; } inline void cal(LL x){ num.clear(); while(x){ num.push_back(x%10); x /= 10; } } inline void cal_k(LL k){ k_num.clear(); k_sum = 0; while(k){ k_sum += k%10; k_num.push_back(k%10); k /= 10; } } LL solve_pos(LL n, LL k){ if (k == 0) return 0; cal_k(k); cal(n); MEM(dp, -1); return dfs(num.size()-1, 0, num.size()-1, 0, 1) + 1; } LL solve_rank(LL n, LL k){ LL sum_num = 0; LL dig_sum_rank = k; for (sum = 1; sum <= 180; sum ++){ MEM(fsum, -1); sum_num += find_dig_sum(num.size()-1, 0, 1); if (sum_num >= k){ k_dig_sum = sum; if (sum > 1){ dig_sum_rank -= sum_num - find_dig_sum(num.size()-1, 0, 1); } break; } } LL testnum = 0, sumranks = 0; while(solve_pos(n, testnum) != k){ testnum *= 10; LL ranks = 0; REP(i, 0, 9){ ranks = 0; if (testnum == 0 && i == 0) continue; LL begin = testnum+i; LL offset = 1; while(begin<=n){ cal(min(n, begin+offset-1)); ranks += find_dig_sum(num.size()-1, 0, 1); cal(begin-1); ranks -= find_dig_sum(num.size()-1, 0, 1); begin *= 10; offset *= 10; } if (sumranks + ranks >= dig_sum_rank){ testnum = testnum + i; break; } sumranks += ranks; } } return testnum; } LL n, k; int main(){ while(scanf("%lld %lld", &n, &k), n+k){ res1 = res2 = -1; res1 = solve_pos(n, k); res2 = solve_rank(n, k); printf("%lld %lld\n", res1, res2); } return 0; } [/cpp]

你可能感兴趣的:(order)