zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    Daily Scrum 12.16
    Daily Scrum 12.15
    Daily Scrum 12.14
    Daily Scrum 12.12
    Daily Scrum 12.11
    Pipeline组Beta版本发布说明
    Pipeline组项目Postmortem
    Pipeline组Alpha版本发布说明
    Pipeline组测试说明
    Daily Scrum 11.6
  • 原文地址:https://www.cnblogs.com/saigyouji-yuyuko/p/10561801.html
Copyright © 2011-2022 走看看