poj3208 Apocalypse Someday[数位DP]

数位中出现至少3个连续的'6'的数字(称魔鬼数),询问满足要求的排名k的数。


经典题型。采用试填法。

递推做法:预处理出$i$位数字中满足要求的数(下记为'魔鬼数')。对每一位都从0到9试一遍,然而卡在了试填时试到6这个数时该怎么办,不太会做。然后才知道可以记录填到目前的上一位已有多少个连续的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]=10*f[i-1][3]+f[i-1][2]$

其中0表示开头有0个6,1表示开头有一个6,2同理,3表示当前小于等于i位的魔鬼数的数量(也就相当于把各位数的魔鬼数累积起来)

注意是算入前导0的。比如有004566674,因为试填时他也算一种。而开头不必担心,看代码第1次循环就会发现考虑0的时候恰好把小于i位的魔鬼数数量都减掉了,说不太清楚,还是自己想想吧。

用k以辅助记录当前填过的末尾连续的6数量,用于处理填6时计算,每填一个6就多算一下。k到3时表示我已经诞生连续3个的6了,所以后面所有的填法都合法,这是要再单独特殊处理,看code。

其他就是常规的试填。

说两点细节:

注意边界处理 ,考虑预处理DP值的边界,尤其是f[0],f[1]等;

试填也要考虑边界。比如开始时。结束时(最后一位),这直接和f[0][0]的初始化产生了联系。所以两个边界都要看一下检查正确性。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<algorithm>
 6 #define dbg(x) cerr<<#x<<" = "<<x<<endl
 7 #define ddbg(x,y) cerr<<#x<<" = "<<x<<"   "<<#y<<" = "<<y<<endl
 8 using namespace std;
 9 typedef pair<int,int> pii;
10 typedef long long ll;
11 template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;}
12 template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;}
13 template<typename T>inline T _min(T A,T B){return A<B?A:B;}
14 template<typename T>inline T _max(T A,T B){return A>B?A:B;}
15 template<typename T>inline T read(T&x){
16     x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1;
17     while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x;
18 }
19 ll f[11][4],rk,cnt;
20 int T;
21 inline void preprocess(){
22     f[1][0]=9,f[1][1]=1;f[0][0]=1;//←注意边界处理 ,DP和试填都要检验一下 
23     for(register int i=2;i<=10;++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]=10*f[i-1][3]+f[i-1][2];
24 }
25 
26 int main(){//freopen("test.in","r",stdin);freopen("test.out","w",stdout);
27     read(T);preprocess();while(T--){
28         read(rk);int x;for(x=3;x<=10;++x)if(rk<=f[x][3])break;
29         for(register int i=x,k=0;i;--i){
30             for(register int j=0;j<=9;++j){
31                 cnt=f[i-1][3];
32                 if(k==3)cnt+=f[i-1][0]+f[i-1][1]+f[i-1][2];
33                 else if(j==6)for(register int l=2-k;l<3;++l)cnt+=f[i-1][l];
34                 if(rk<=cnt){
35                     if(k<3){if(j==6)++k;else k=0;}
36                     printf("%d",j);break;
37                 }
38                 else rk-=cnt;
39             }
40         }
41         puts("");
42     }
43     return 0;
44 }
View Code

然后讲一下记搜的,可以套模板。求第k大的数,可以转化为二分枚举区间[1,n]的右边界n,看[1,n]的满足要求的数,由于其随区间变大而单调增,所以是二分。通过不断调整,可以找到最小n使得区间[1,n]恰有k个满足要求的数。这个因为是套模板,所以很好写。但是二分的话会让复杂度多一个log。。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<algorithm>
 6 #define dbg(x) cerr<<#x<<" = "<<x<<endl
 7 using namespace std;
 8 typedef long long ll;
 9 template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;}
10 template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;}
11 template<typename T>inline T _min(T A,T B){return A<B?A:B;}
12 template<typename T>inline T _max(T A,T B){return A>B?A:B;}
13 template<typename T>inline T read(T&x){
14     x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1;
15     while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x;
16 }
17 int T;
18 ll L,R,f[12][2][2][2],b[12],aim;
19 ll dp(int len,bool las,bool las2,bool fil,bool limit){
20     if(!len)return fil;
21     if(!limit&&~f[len][las][las2][fil])return f[len][las][las2][fil];
22     ll cnt=0;int num=limit?b[len]:9;
23     for(register int i=0;i<=num;++i)cnt+=dp(len-1,i==6,las,fil||(las&&las2&&i==6),limit&&i==num);
24     return limit?cnt:f[len][las][las2][fil]=cnt;
25 }
26 inline ll solve(ll x){
27     int k=0;while(x)b[++k]=x%10,x/=10;
28     return dp(k,0,0,0,1);
29 }
30 
31 int main(){//freopen("tmp.txt","r",stdin);//freopen("test.out","w",stdout)£»
32     memset(f,-1,sizeof f);
33     read(T);while(T--){
34         read(aim);L=666,R=1e10;ll mid;
35         while(L<R){
36             mid=L+R>>1;
37             if(solve(mid)<aim)L=mid+1;
38             else R=mid;
39         }
40         printf("%lld
",L);
41     }
42     return 0;
43 }
View Code
原文地址:https://www.cnblogs.com/saigyouji-yuyuko/p/10561801.html