/*
题目:完美数
题目链接: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; }