D - Lunlun Number
此题本人是用 (dp+构造) 做的。
设 (dp[i][j]) 代表以 (i) 开头,有 (j) 位的lunlun数有多少个。
根据样例的提示,其实 (j) 最大也就 (10)。(i) 在 ([0,9])。
首先初始化所有 (dp[i][1]=1)。
然后不难想到转移方程 (dp[i][j]=dp[i-1][j-1]+dp[i][j-1]+dp[i+1][j-1])。
当然如果 (i=0或i=9) 时要特殊处理(具体见代码)。
接下来开始构造。
首先算出第 (k) 个数的位数。
这很好办,我们只用统计每个位数有多少lunlun数,然后做一遍前缀和,从小到大遍历,找到第一个大于等于 (k) 的位数即为 (k) 的位数。
确定好位数之后我们就可以开始枚举每一位是什么了。
当然不是暴力枚举。
如果是第一位,我们暴力枚举 (1-9),用找位数同样的方法,我们可一个确定第一个位置的数(我们记录了以 (i) 开头,有 (j) 位的lunlun数有多少个)。
然后后面的位置的数只有最多三种选择——上次减一,上次同样和上次加一(记住不能小于 (0) 或大于 (9))。
然后再用相同的方法即可。
#include <iostream>
#include <algorithm>
#include <map>
#include <cmath>
using namespace std;
typedef long long ll;
ll dp[10][20];//dp[i][j]代表以i开头的j位数有多少个lunlun
ll sum[20], pos;
ll k;
void dfs(ll num, ll lst) {
if (num > pos) return;
if (num == 1) {
for (ll i = 1; i < 10; i++) {
if (k > dp[i][pos - num + 1]) {
k -= dp[i][pos - num + 1];
} else {
cout << i;
dfs(num + 1, i);
break;
}
}
} else {
for (ll i = lst - 1; i <= lst + 1; i++) {
if (i < 0 || i > 9) continue;//记住特判
if (k > dp[i][pos - num + 1]) {
k -= dp[i][pos - num + 1];
} else {
cout << i;
dfs(num + 1, i);
break;
}
}
}
}
int main() {
cin >> k;
for (ll i = 0; i < 10; i++) {
dp[i][1] = 1;
}
sum[1] = 9;
for (ll j = 2; j <= 10; j++) {
for (ll i = 0; i < 10; i++) {
if (i != 0 && i != 9) dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1] + dp[i + 1][j - 1];
else if (i != 0) dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
else if (i != 9) dp[i][j] = dp[i][j - 1] + dp[i + 1][j - 1];
if (i != 0) sum[j] += dp[i][j];
}
}
for (ll i = 1; i <= 20; i++) {
if (k > sum[i]) {
k -= sum[i];
} else {
pos = i;
break;
}
}
dfs(1, 0);
return 0;
}