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

题意:

多组数据,每组求第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;
}



你可能感兴趣的:(数位dp,POJ3208,数位树,0ms)