POJ-3208 Apocalypse Someday (数位DP)

只要某数字的十进制表示中有三个6相邻,则该数字为魔鬼数,求第X小的魔鬼数\(X\le 5e7\)

这一类题目可以先用DP进行预处理,再基于拼凑思想,用“试填法"求出最终的答案

\(F[i,3]\)表示由 \(i\) 位数字构成的魔鬼数有多少个,\(F[i,j](0\le j\le 2)\) 表示由 \(i\) 位数字构成的,开头已经有连续 \(j\) 个6的非魔鬼数有多少个。(允许前导0的存在,想一想为什么)
转移方程

  1. \(F[i,0] = 9*(F[i-1,0] + F[i-1,1] + F[i-1,2])\)
  2. \(F[i,1] = F[i-1,0]\)
  3. \(F[i,2] = F[i-1,1]\)
  4. \(F[i,3] = F[i-1,2] + 10 * F[i-1,3]\)

然后一位一位的试填,要注意前面填过的数字结尾如果有 k 个6,通过后面拼接 3-k 个6也可以构成魔鬼数

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
ll f[21][4];
int T,n,l;
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();
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        //l为答案的长度
        for(l=3;f[l][3] < n;l++);
        //k表示填过的数字末尾有k个6
        for(int i=l,k=0;i;i--){
            for(int j=0;j<=9;j++){
                ll cnt = f[i-1][3];//后面预处理出的魔鬼数
                //找能够拼凑出来的魔鬼数
                if(j == 6 || k == 3){
                    if(k == 3){
                        for(int x = 0;x < 3;x++)
                            cnt += f[i-1][x];
                    }else{
                        for(int x = max(3-k-1, 0);x<3;x++){
                            cnt += f[i-1][x];
                        }
                    }
                }
                if(cnt < n) n -= cnt;
                else{
                    if(k < 3) j == 6 ? k ++ : k=0;
                    printf("%d",j);break;
                }
            }
        }
        cout<<endl;
    }
    return 0;
}
posted @ 2019-07-30 12:52  kpole  阅读(151)  评论(0编辑  收藏  举报