题意
定义两个数的比较方法,各位数字之和大的数大,如果数字和相等则按字典序比较两个数的大小。输入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
", res1, res2);
}
return 0;
}
[/cpp]