数位DP———POJ 3208 启示录

POJ3208 启示录

题目传送门

题目大意:我们称只要某数字的十进制表示中有三个连续的6,我们就称它为“魔鬼数”,比如:666,1666,6663,16666 等。现给出一个数X,求“第X小的魔鬼数”。 X ≤ 5 × 1 0 7 X\le 5\times 10^7 X5×107,一共有T组数据点, T ≤ 1000 T\le1000 T1000

$ part.1 $ 引入

这一道题显然是一道数位DP的题目,那么我们先来了解一下什么是数位DP

众所周知
数位DP是与数字相关的一类计数问题,它往往指的是这样的题型:

给定一个闭区间 [ l , r ] [l,r] [l,r],让你求这个区间中满足某种条件的数的个数。

对于这类题目,我们通常先用动态规划进行预处理,再基于拼凑思想,用“试填法”求出最终的答案。

$ part .2 $ 具体分析

面对这样的一道题,如果直接每次都枚举到第X个“魔鬼数”,肯定会超时。那么我们思考一下如何解决这个问题呢?

其实,这是一道数位DP的典型例题,根据其通常做法,我们可以打出一个表记录一下魔鬼数的个数,然后通过一些
玄学 ”的处理,来巧妙计算出第X个“魔鬼数”

p a r t . 3 part.3 part.3 步骤实现

1. 1. 1. 预处理

我们设 F [ i , 3 ] F [i,3] F[i,3]表示由i位数字构成的魔鬼数有多少个,
F [ i , j ] ( 0 ≤ j ≤ 2 ) F [i,j] (0\le j \le2 ) F[i,j](0j2) 表示由 i i i位数字构成,开头已经有连续 j j j个6的非魔鬼数有多少个。(注意:在计算F时,我们允许前导0存在)。

实际上,在这里** F [ i , 3 ] F [i,3] F[i,3] F [ i , j ] F[i,j] F[i,j]具有的含义是不同的。所以这两者不是以同一种方式转移的**

对于F [ i , 3 i,3 i,3] :它代表的是由 i i i位数字组成含有连续3个6的总方案数,这里的6并不一定从第 i i i位开始,

然而对于F [ i , j i,j i,j] :它代表的是从第 i i i位开始的有 j j j个6的方案数。

那么,在考虑第 i i i位(最高位)是什么数字时,易得转移方程:

  • F [ i , 0 ] F [i,0] F[i,0] = 9 × 9\times 9×( F [ i − 1 , 0 ] + F [ i − 1 , 1 ] + F [ i − 1 , 2 ] ) F[i-1,0]+F[i-1,1]+F[i-1,2]) F[i1,0]+F[i1,1]+F[i1,2])
  • F [ i , 1 ] = F [ i − 1 , 1 ] F [i,1] = F [i-1,1] F[i,1]=F[i1,1]
  • F [ i , 2 ] = F [ i − 1 , 2 ] F [i,2] = F [i-1,2] F[i,2]=F[i1,2]

简要概述一下上述转移方程:

首先,对于 j = 0 j=0 j=0 的情况,即第 i i i位不放6,此时第 i i i位可以考虑从 0 ∼ 9 0\sim9 09除6以外的所有情况,此时一共有9种选择,而 i − 1 i-1 i1位往后,无论选了一个6 o r or or 两个6都可以向此状态转移。

其次,再考虑 j = 1 j=1 j=1 j = 2 j=2 j=2的情况,此时就很显然第 i − 1 i-1 i1位必须选一个或者两个,不过多赘述。

L a s t    n o t    t h e    l e a s t Last\:\:not\:\:the\:\:least Lastnottheleast

F [ i , 3 ] F [i,3] F[i,3]的转移就不一样了,它又该分2种情况讨论:

首先,是三个6从第 i i i位开始往下排,此时应该显然由 F [ i − 1 , 2 ] F [i-1,2] F[i1,2]的方案数转移过来,
然后由于还要继承前 i − 1 i-1 i1位的方案数,
又由于第 i i i位从 0 ∼ 9 0\sim9 09都可以选,且不会与之前重复,所以应该是 10 × F [ i − 1 , 3 ] 10\times F[i-1,3] 10×F[i1,3]

至此,完成动态规划后,预处理就完成了,我们就可以开开心心,快快乐乐的进入下一步了。

2. 2. 2. 试填法 解决问题

至此我们从左到右依次考虑每一位,同时记录当前末尾已经有连续的几个6。
从小到大枚举当前数位填的数字,通过预处理的 F F F数组来直接计算魔鬼数的个数,与 X X X比较即可。

下面就是激动人心的上代码时间了 ! ! !

$ part.3$ C o d e Code Code

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
ll f[22][4];//
int T,x;
int main() {
    
    
    scanf("%d",&T);
    f[0][0]=1;
    for(int i=1; i<=20; i++) {
    
    //DP预处理
        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];
    }

    while(T--) {
    
    
        int m=0;
        scanf("%d",&x);
        for(m=3; f[m][3]<x; m++);//枚举来确定第X个魔鬼数的位数m;
        int k=0;//k表示从i+1位向前有连续k个6;
        for(int i=m; i>=1; i--)
            for(int j=0; j<=9; j++) {
    
    
                ll cnt=f[i-1][3];//前i位且第i位为j时的总的方案数; 
                if(j==6 || k==3)
                    for(int l=max(3-k-(j==6),0); l<3; l++) 
                        cnt+=f[i-1][l];
                if(cnt<x)//如果cnt比x小,说明当前位的j小于答案,进行累积并进入下一层; 
                    x-=cnt;
                else {
    
    //否则,可确定j为当前的答案; 
                    if(k<3) {
    
    //当k大于三等于的时候后面无论怎样都是魔鬼数了,就不用考虑当前是否为6了,可以直接记录答案
                        if(j==6)
                            k++;
                        else
                            k=0;
                    }
                    printf("%d",j);
                    break;
                }
            }
        puts("");
    }
}

P . S P.S P.S

这是本蒟蒻的第一篇题解,写出来还是很激动的,看着这一篇完完整整的题解,很有成就感。谨以这一篇题解纪念我接近两年的 O I OI OI生涯吧。(估计考完就退役了

还有最后7天就要参加 C S P − S CSP-S CSPS的比赛了,希望 R P + + !   !   ! RP++!\:!\:! RP++!!!

猜你喜欢

转载自blog.csdn.net/zoiert/article/details/109395391