POJ 3208-Apocalypse Someday【数位DP+二分】

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_41785863/article/details/101978651

题意:给你一个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;
}

猜你喜欢

转载自blog.csdn.net/qq_41785863/article/details/101978651