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; }