【POJ3208】传说中POJ最难的数位DP?(正解AC自动机,二解数位DP,吾异与之)

来源:互联网 发布:淘宝嘉年华报名要求 编辑:程序博客网 时间:2024/05/15 22:59

题意:

多组数据,每组求第n个包含‘666’的数(不能断开),如1:666,2:1666,14:6667。

题解:

AC自动机解法没去想,数位DP没学,这里有一种类似于数位DP,却又与数位DP不同,我称为数位树。

数位树:

将数n如线段树一样地拆分成多个小段,进行递归处理得出答案。

本题详(lue)解:

直接看每一位应该是什么数,然后n减去相应的数,使得在下一层转换为子问题“在开头有b个连续的6时,求第a个带‘666’的数”。就是如此简单,如此简单!!!!


代码来啦!

#include <cstdio>#include <cstring>#include <algorithm>#define N 20/*数的最大保证位数*/using namespace std;long long n;long long f0[N],f1[N],f2[N],f3[N];long long f10[N],f5[N],f6[4][N];/*注一:[N]处为位数注二:这里说的数不去首位0,即0066算4位数f0:首位不为6且不含‘666’的数的个数f1:首位  为6且不含‘666’的数的个数(第二位不为‘6’)f2:首位  为6且不含‘666’的数的个数(第二位  为‘6’)f3:含‘666’的数的个数f5:5开头的含‘666’的数的个数f6:1:开头含‘6’的数的个数2:开头含‘66’的数的个数3:含‘666’的数的个数f10:同f3*/long long power(long long x,long long p){long long ans=1;while(p>0){if(p&1)ans*=x;x*=x;p>>=1;}return ans;}void dfs(long long nn,long long nm)/*位数,已连续的6的个数*/{if(!nn)/*没有位了,结束!*/{printf("\n");return ;}long long i,j,k;if(nm>=3)/*剪枝(如果前面已经‘666’了,那么直接算数就好)*/{long long tens=1;n--;for(i=1;tens<=n;i++,tens*=10);for(;i<=nn;i++)printf("0");if(n)printf("%I64d",n);printf("\n");return ;}if(f5[nn]>=n)/*此位为0~5*/{printf("%I64d",(n-1)/f10[nn-1]);n=(n-1)%f10[nn-1]+1;dfs(nn-1,0);return ;}else if(f6[3-nm][nn]>=n)/*此位为6*/{printf("6");n-=f5[nn];dfs(nn-1,nm+1);return ;}else/*此位为7~9*/{n-=f6[3-nm][nn];printf("%I64d",(n-1)/f10[nn-1]+7);n=(n-1)%f10[nn-1]+1;dfs(nn-1,0);return ;}}void init()/*预处理*/{long long i;f0[0]=1;for(i=1;i<N;i++){f0[i]=(f0[i-1]+f1[i-1]+f2[i-1])*9;f1[i]=f0[i-1];f2[i]=f1[i-1];f3[i]=f3[i-1]*10+f2[i-1];}for(i=3;i<N;i++)/*i位数中含‘666’的数的个数*/{f10[i]=f3[i];f5[i]=6*f10[i-1];f6[3][i]=f10[i]-3*f10[i-1];f6[2][i-1]=f6[3][i]-f5[i]-3*f10[i-2];f6[1][i-2]=f6[2][i-1]-f5[i-1]-3*f10[i-3];}}int main(){long long i,g;scanf("%I64d",&g);init();while(g--){scanf("%I64d",&n);for(i=3;f10[i]<n;i++);dfs(i,0);}return 0;}



1 0
原创粉丝点击