POJ3208 启示录
题目传送门
题目大意:我们称只要某数字的十进制表示中有三个连续的6,我们就称它为“魔鬼数”,比如:666,1666,6663,16666 等。现给出一个数X,求“第X小的魔鬼数”。 X ≤ 5 × 1 0 7 X\le 5\times 10^7 X≤5×107,一共有T组数据点, T ≤ 1000 T\le1000 T≤1000。
p a r t . 1 part.1 part.1 引入
这一道题显然是一道数位DP的题目,那么我们先来了解一下什么是数位DP
众所周知
数位DP是与数字相关的一类计数问题,它往往指的是这样的题型:
给定一个闭区间 [ l , r ] [l,r] [l,r],让你求这个区间中满足某种条件的数的个数。
对于这类题目,我们通常先用动态规划进行预处理,再基于拼凑思想,用“试填法”求出最终的答案。
p a r t . 2 part .2 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](0≤j≤2) 表示由
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[i−1,0]+F[i−1,1]+F[i−1,2])
- F [ i , 1 ] = F [ i − 1 , 0 ] F [i,1] = F [i-1,0] F[i,1]=F[i−1,0]
- F [ i , 2 ] = F [ i − 1 , 2 ] F [i,2] = F [i-1,2] F[i,2]=F[i−1,2]
简要概述一下上述转移方程:
首先,对于 j = 0 j=0 j=0 的情况,即第 i i i位不放6,此时第 i i i位可以考虑从 0 ∼ 9 0\sim9 0∼9除6以外的所有情况,此时一共有9种选择,而 i − 1 i-1 i−1位往后,无论选了一个6 o r or or 两个6都可以向此状态转移。
其次,再考虑 j = 1 j=1 j=1与 j = 2 j=2 j=2的情况,此时就很显然第 i − 1 i-1 i−1位必须选一个或者两个,不过多赘述。
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[i−1,2]的方案数转移过来,
然后由于还要继承前
i
−
1
i-1
i−1位的方案数,
又由于第
i
i
i位从
0
∼
9
0\sim9
0∼9都可以选,且不会与之前重复,所以应该是
10
×
F
[
i
−
1
,
3
]
10\times F[i-1,3]
10×F[i−1,3]
至此,完成动态规划后,预处理就完成了,我们就可以开开心心,快快乐乐的进入下一步了。
2. 2. 2. 试填法 解决问题
至此我们从左到右依次考虑每一位,同时记录当前末尾已经有连续的几个6。
从小到大枚举当前数位填的数字,通过预处理的
F
F
F数组来直接计算魔鬼数的个数,与
X
X
X比较即可。
下面就是激动人心的上代码时间了
!
!
!
p a r t . 3 part.3 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 CSP−S的比赛了,希望 R P + + ! ! ! RP++!\:!\:! RP++!!!