Apocalypse Someday (POJ - 3208,数位 DP)

一.题目链接:

POJ-3208

二.题目大意:

定义魔鬼数为含有连续 666 的数.

求第 k 大的魔鬼数.

三.分析:

很明显用填坑法解数位 DP.

假设当前枚举到数字的第 i 位且第 i 位为 j ,那我们需要知道小于等于当前枚举的数的魔鬼数有多少个.

显然需要预处理出 f[i][j].

f[i][0]: 不超过 i 位数且从第 i 位开始有 0 个连续的 6 的非魔鬼数的数的个数.

f[i][1]: 不超过 i 位数且从第 i 位开始有 1 个连续的 6 的非魔鬼数的数的个数.

f[i][2]: 不超过 i 位数且从第 i 位开始有 2 个连续的 6 的非魔鬼数的数的个数.

f[i][3]: 不超过 i 位数的魔鬼数的数的个数.

易得状态转移方程为:

f[i][0] = (f[i - 1][0] + f[i - 1][1] + f[i - 1][2]) * 9

f[i][1] = f[i - 1][0]

f[i][2] = f[i -1][1]

f[i][3] = f[i - 1][2] + f[i - 1][3] * 10

详见代码.

四.代码实现:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;

ll f[25][4];

void init()
{
    f[0][0] = 1;
    for(int i = 1; i <= 20; ++i)
    {
        f[i][0] = (f[i - 1][0] + f[i - 1][1] + f[i - 1][2]) * 9;
        f[i][1] = f[i - 1][0];
        f[i][2] = f[i - 1][1];
        f[i][3] = f[i - 1][2] + f[i - 1][3] * 10;
    }
}

int main()
{
    init();
    int T;
    scanf("%d", &T);
    while(T--)
    {
        int n, m;
        scanf("%d", &n);
        for(m = 1; f[m][3] < n; ++m);
        for(int i = m, k = 0; i >= 1; --i)
        {
            for(int j = 0; j <= 9; ++j)
            {
                ll cnt = f[i - 1][3];
                if(j == 6 || k == 3)
                {
                    for(int l = max(0, 3 - k - (j == 6)); l < 3; ++l)
                    {
                        cnt += f[i - 1][l];
                    }
                }
                if(cnt < n) n -= cnt;
                else
                {
                    if(k < 3)
                    {
                        if(j == 6)  ++k;
                        else        k = 0;
                    }
                    printf("%d", j);
                    break;
                }
            }
        }
        printf("\n");
    }
    return 0;
}

 

猜你喜欢

转载自blog.csdn.net/The___Flash/article/details/104892238