POJ3208魔鬼数

题目:http://poj.org/problem?id=3208

与一般的数位dp有点不同的是,没有给出上界,而是要通过值来判断这一位该填什么。

当然是从高位向低位填。

为了知道这一位填下去对答案有什么影响,需要预处理出后面无限制的魔鬼数个数。

预处理魔鬼数最重要的是不重不漏。这一位的魔鬼数=上一位的所有魔鬼数+这一位填6带来的新魔鬼数。

新魔鬼数不能与上一位已有的魔鬼数重复,所以需要记录“开头有2个6的魔鬼数”。

为了得到这个,递推需要记录“开头有1个6的非魔鬼数”和“开头有0个6的非魔鬼数”。

f [ i ][ 0 ]=9*f [ i-1 ][ 0 ]+9*f [ i-1 ][ 1 ]+9*f [ i-1 ][ 2 ];  //不填6

f [ i ][ 1 ]=f [ i-1 ][ 0 ];  f [ i ][ 2 ]=f [ i-1 ][ 1 ];  f [ i ][ 3 ]=f [ i-1 ][ 2 ];  //填6

代码中n-=cnt意思是这一位越过这个j之后,当前累计魔鬼数数量就多了cnt个。就像普通数位dp一样。

看了蓝皮书上的精美写法!竟然可以用一句for给m赋值!l 的循环也写的很好!

dp的初值是自己不熟的地方。

别忘了输出当前位之后要break。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int LM=20;
int n,m,k,t;
long long f[LM+5][5];
void pre()
{
    f[0][0]=1;//
    for(int i=0;i<LM;i++)
    {
        for(int j=0;j<3;j++)
        {
            f[i+1][j+1]+=f[i][j];
            f[i+1][0]+=9*f[i][j];
        }
        f[i+1][3]+=10*f[i][3];
    }
}
int main()
{
    pre();
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        k=0;
        for(m=0;f[m][3]<n;m++);
        for(int i=m;i;i--)//i
            for(int j=0;j<=9;j++)
            {
                long long cnt=f[i-1][3];
                if(j==6||k==3)
                    for(int l=max(3-k-(j==6),0);l<3;l++)
                        cnt+=f[i-1][l];
    //            printf("i=%d j=%d cnt=%lld n=%d\n",i,j,cnt,n);
                if(cnt<n)n-=cnt;
                else
                {
                    if(k<3)
                    {
                        if(j==6)k++;
                        else k=0;
                    }
                    printf("%d",j);
    //                printf("i=%d j=%d\n",i,j);
                    break;
                }
            }
        printf("\n");
    }
    return 0;
}

 

posted on 2018-03-27 22:41  Narh  阅读(315)  评论(0编辑  收藏  举报

导航