poj3208 启示录 数位dp

【一句话题意】定义十进制下有3个连续的6的数为魔鬼数。有T个询问,求第k小的魔鬼数。
T<=1000,k<=5e7
【分析】由于K有5e7那么大,哪怕线性dp,常数稍大就会有TLE的风险。如果内存小于128MB又会有MLE的问题显然,预处理出第k大的魔鬼数是不可靠的。
由于T较小,我们转而考虑能否像计数dp一样将先大致预处理出辅助数组,再进行“拼凑”。回答是可行的。但dp数组的定义是与数字的位数有关。
定义f[i,k]表示i位数,开头有k个连续的6的数的数目。注意允许出现前导0
状态转移方程:
f [ i , 0 ] = 9 ∗ ( f [ i − 1 , 0 ] + f [ i − 1 , 1 ] + f [ i − 1 , 2 ] f[i,0]=9*(f[i-1,0]+f[i-1,1]+f[i-1,2] f[i,0]=9(f[i1,0]+f[i1,1]+f[i1,2]
f [ i , 1 ] = f [ i − 1 , 0 ] f[i,1]=f[i-1,0] f[i,1]=f[i1,0]
f [ i , 2 ] = f [ i − 1 , 1 ] f[i,2]=f[i-1,1] f[i,2]=f[i1,1]
f [ i , 3 ] = f [ i − 1 , 2 ] + f [ i − 1 , 3 ] ∗ 10 f[i,3]=f[i-1,2]+f[i-1,3]*10 f[i,3]=f[i1,2]+f[i1,3]10
每次在询问时,利用辅助数组进行转移。
注意设计状态转移方程时,注意不重复不遗漏
【code】

#include
#include
#include
#include
using namespace std;
int f[200][4];
void Init(){
	f[0][0]=1;
	for(int i=0;i<=20;i++){
		for(int j=0;j<3;j++){
			f[i+1][j+1]+=f[i][j];
			f[i+1][0]+=f[i][j]*9;
		}
		f[i+1][3]+=f[i][3]*10;
	}
}
int main(){
	int T,n,m;cin>>T;
	Init();
	while(T--){
		scanf("%d",&n);m=3;
		while(f[m][3]<n)m++;
		for(int i=m,k=0;i>0;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];
				if(cnt<n) n-=cnt;
				else{
					if(k<3){
						if(j==6) k++;else k=0;
					}
					printf("%d",j);
					break;
				}
			}
		}
		cout<<endl;
	}
	return 0;
}

你可能感兴趣的:(HG集训,动态规划,优化暴力,数位dp)