acdream 数树专题--完美数(数位dp)

/*
 题目:完美数
 题目链接:http://www.acdream.net/problem.php?id=1083
 题目描述:在[L , R] 的正整数区间内,要么包含3 要么包含 8 的不同的整数有多少个?
 
 解题思路:数位dp;
 
 这题主要求出[0,n]区间内有多少个这样的数;
 则[L,r]=[0,r]-[0,l-1];

 先求出每位数以内有多少这样的数,比如:
 dp[i][1]表示在i位数中只包含3不包含8的数的个数
 dp[i][2]表示在i位数中只包含8不包含3的数的个数,
 dp[i][3]表示在i位数中既不包含8也不包含3的数的个数。
 
 注:i位数就是[0,10^i)以内的数;
 
 这有状态方程转移为:
 dp[i][1]=dp[i-1][3]+dp[i-1][1]*9; //表示这一位取3,前面所有的位既不含8也不含3,加上这一位不取8(剩下9个数),上一位数中只取3的数
 dp[i][2]=dp[i-1][3]+dp[i-1][2]*9; //表示这一位取8,前面所有的位既不含8也不含3,加上这一位不取3(剩下9个数),上一位数中只取3的数
 dp[i][3]=dp[i-1][3]*8;     //表示这一位既不取8也不取3,上一位也既不取3,8的数,那么这一位能取的个数就是8;

 我们求出某位数之内有多少个这样的数之后,接下来就是逐位分析了;

 比如我们要求n以内的所有数,假设n=1386;我们先取n的最高位1,因为最高位是1,那么从0到999以内的数我们可以直接加上(dp[3][1]+dp[3][2]),

 接下来的位是3,那么0-299之内的数我们可以加上3*(dp[2][1]+dp[2][2]),因为是3,我们标记一下3已经出现了,接下来一位是8,我们可以加上0-79
 
 之内的数8*dp[1][1](因为已经出现了3,所以dp[1][2]就不能要了),这里还要注意一下,因为前面出现了3,那么我们还要加上8*dp[1][3]。又因为现在
 
 出现了8,再往下就没意义了(8和3不能同时出现),最后答案就是上面的所有数的和。

 如果不是因为3和8矛盾跳出循环的,还要考虑最后一位的数字;
 
 注意:逐位分析的时候一定要充分考虑位的相关性,以下是我的代码;


*/

 

#include<stdio.h>
#include<string.h>

int dp[15][4];
void init()
{
	int i;
	memset(dp,0,sizeof(dp));
	dp[0][3]=1;
	dp[1][1]=1;
	dp[1][2]=1;
	dp[1][3]=8;
	for(i=2;i<11;i++)
	{
		dp[i][1]=dp[i-1][3]+dp[i-1][1]*9;
		dp[i][2]=dp[i-1][3]+dp[i-1][2]*9;
		dp[i][3]=dp[i-1][3]*8;
	}
}
int find(int n)
{
	int digit[10],i,cnt,tag1=0,tag2=0;
	if(n<3)return 0;
	int sum=0,len=0;
	while(n){digit[++len]=n%10;n/=10;}
	for(i=len;i>0;i--)
	{
		if(tag1&&tag2)break;
		cnt=0;
		if(tag1||tag2)sum+=digit[i]*(dp[i-1][3]);
		if(digit[i]>3)
		{
			cnt++;
			if(!tag1) sum+=dp[i-1][2]+dp[i-1][3];
			if(tag1||tag2) sum-=dp[i-1][3];
		}
		if(digit[i]>8){
			cnt++;
			if(!tag2) sum+=dp[i-1][3]+dp[i-1][1];
			if(tag1||tag2) sum-=dp[i-1][3];
		}
		
		if(!tag2)sum+=(digit[i]-cnt)*(dp[i-1][1]);
		if(!tag1)sum+=(digit[i]-cnt)*(dp[i-1][2]);
		if(digit[i]==3)
			tag2=1;
		else if(digit[i]==8)
			tag1=1;
	}
	if(tag1==1-tag2)sum++;
	return sum;
}
int main()
{

	int cas,i,l=0,r,sum=0;
	init();
	scanf("%d",&cas);
	while(cas--)
	{
		scanf("%d%d",&l,&r);
		if(l>r){i=l;l=r;r=i;}
		r=find(r);
		l=find(l-1);
		printf("%d\n",r-l);
	}
	return 0;
}


 

/*
   记忆化搜索版-(原理不变,代码更简洁)
 
 (记忆化搜索版)数位dp资料详见博客:http://www.cppblog.com/Yuan/archive/2011/07/15/139299.html

*/

#include<stdio.h>
#include<string.h>

int dp[11][3],digit[11];

int dfs(int pos,int pre,int doing)
{
	int i,now,ans=0;
	if(pos==0) return pre>0;
	if(!doing&&dp[pos][pre]!=-1)return dp[pos][pre];
	now=doing?digit[pos]:9;
	for(i=0;i<=now;i++)
	{
		if(i==3) ans+=(pre!=2)?dfs(pos-1,1,doing&&i==now):0;
		else if(i==8) ans+=(pre!=1)?dfs(pos-1,2,doing&&i==now):0;
		else ans+=dfs(pos-1,pre,doing&&i==now);
	}
	if(!doing) dp[pos][pre]=ans;
	return ans;
}

int cal(int n)
{
	if(n<3)return 0;
	int len=0;
	while(n){ digit[++len]=n%10;n/=10;}
	return dfs(len,0,1);
}
int main()
{
	memset(dp,-1,sizeof(dp));

	int cas,l,r;
	scanf("%d",&cas);
	while(cas--)
	{
		scanf("%d%d",&l,&r);
		printf("%d\n",cal(r)-cal(l-1));
	}
	return 0;
}


 

你可能感兴趣的:(acdream 数树专题--完美数(数位dp))