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

来源:互联网 发布:网络歌手想你的感觉 编辑:程序博客网 时间:2024/05/20 05:25

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


 

原创粉丝点击