版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
题意:给你一个n(n<5e7),让你找到第n个包含“666”的数。
思路:妥妥的数位DP,我们可以考虑二分答案,左边界为0,右边界我提前试了一下发现在(1<< 32)之内,我们不断逼近左边界一定可以找到答案,需要注意的是会计算上0,所以要减去1
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
#define mid ((l + r) >> 1)
const int maxn = 1e5 + 10;
const ll inf = 0x3f3f3f3f;
ll dp[35][35][35];
int a[35];
ll dfs(int pos, int pre1, int pre2, int lead, int limit)
{
if(pos == 0) return 1;
if(!limit && !lead && dp[pos][pre1][pre2] != -1)
return dp[pos][pre1][pre2];
int up = limit ? a[pos] : 9;
ll ret = 0;
for(int i = 0; i <= up; ++i)
{
if(pre1 == 6 && pre2 == 6 && i == 6) continue;
ret += dfs(pos - 1, pre2, i, lead && (i == 0), limit && (i == up));
}
if(!limit && !lead) dp[pos][pre1][pre2] = ret;
return ret;
}
ll slove(ll x)
{
int tot = 0;
while(x)
{
a[++tot] = x % 10;
x /= 10;
}
return dfs(tot, 0, 0, 1, 1);
}
int main()
{
int t;
scanf("%d", &t);
memset(dp, -1, sizeof(dp));
while(t--)
{
int n;
scanf("%d", &n);
ll l = 0, r = (1ll << 33);
ll ans = 0;
while(l <= r)
{
if((mid - slove(mid) + 1) >= n)
ans = mid, r = mid - 1;
else
l = mid + 1;
}
printf("%lld\n", ans);
}
return 0;
}
另外一种方法是试填法,先找到在多少位之内,然后不断尝试填,看答案在哪里。
f[i][j] 表示第i位且有连续j个6时的答案,那么转移方程就是
f[i][0] = 9 * (f[i - 1][0] + f[i - 1][1] + f[i - 1][2]);
f[i][1] = f[i - 1][0];
f[i][2] = f[i - 1][1];
f[i][3] = f[i - 1][2] + 10 * f[i - 1][3];
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
#define ls rt << 1
#define rs rt << 1|1
#define mid ((l + r) >> 1)
#define lson l, mid, ls
#define rson mid + 1, r, rs
const int maxn = 25;
ll f[maxn][4]; //表示第i位且有连续j个6时的个数
void init() //预处理一下
{
f[0][0] = 1;
for(int i = 1; i <= 20; ++i)
{
f[i][0] = 9 * (f[i - 1][0] + f[i - 1][1] + f[i - 1][2]);
f[i][1] = f[i - 1][0];
f[i][2] = f[i - 1][1];
f[i][3] = f[i - 1][2] + 10 * f[i - 1][3];
}
}
int main()
{
init();
int t;
scanf("%d", &t);
while(t--)
{
int n, x;
scanf("%d", &n);
for(x = 3; f[x][3] < n; ++x); //先找到第x位,然后再从小到大找
for(int i = x, k = 0; i > 0; --i) //试填第i位
{
for(int j = 0; j <= 9; ++j) //枚举第i位填的数字
{
ll tot = f[i - 1][3];
if(j == 6 || k == 3)
{
for(int l = max(3 - k - (j == 6), 0); l < 3; ++l)
tot += f[i - 1][l];
}
if(tot < n) //第i位应该比j大
n -= tot;
else //否则这一位就是满足条件的
{
if(k < 3)
{
if(j == 6) ++k;
else k = 0;
}
printf("%d", j);
break;
}
}
}
printf("\n");
}
return 0;
}