题目意思就是求1到n出现过几个1(例如n为12时,包含了5个1。1,10,12共包含3个1,11包含2个1,总共5个1)
题目思路就是数位dp,其实可以找规律,但本人太懒了,太麻烦了不想找。数位dp首先要知道dp记录的是什么,这里dp[i][j]指0到第i位数值为j的1的数量(比如dp[3][2](没有上限的情况下)则记录的是0到299有多少个1)。这里会遇到三种情况:
1.j不是1,则继续搜索
2.j是1,并且不是上限则dp[i][j]=pow(10,i-1)+ dp[1~i-1][0~9(或上限)](比如n=213,dp[3][1]指的就是100到199 有pow(10,2)个1再加低位的数量即0到199中1的个数)
for(int i=j-1;j>=1;j++)
3,j是1,并且是上限则dp[i][j]= + a[i]*pow(10,i-1) +1 +dp[1~i-1][0~9(或上限)](比如n=213,dp[2][1]指的就是10到13所以是4个这是+1的原因(1后面全是0的情况),再加低位的数量即0到13中1的个数)
代码:
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #define ll long long #define inf 0x3f3f3f3f #include<cmath> using namespace std; int dp[15][15];//dp[i][j]指0到第i位 数值为j的1的个数 int a[15];//a[i]指n第i位的数 int dfs(int pos,int num,bool limit) { if(pos==0) return 0; if(!limit&&dp[pos][num]!=-1)//已经被记忆了,直接用 return dp[pos][num]; int ans=0; int k=limit?a[pos-1]:9; if(num==1) { if(limit)//该数字是1,正好是上限,例如1342 那么他有342个数再加1(1000)个有1 的数(即343个) { for(int i=pos-1;i>=1;i--) ans+=a[i]*pow(10,i-1); ans++;//1000..的情况 } else//没有上限则就是10^(pos-1) 比如1000 他就是1000个1 { ans+=pow(10,pos-1); } } for(int i=0;i<=k;i++) { ans+=dfs(pos-1,i,limit&&i==a[pos-1]);//搜索下一位 } if(!limit)//记忆化 dp[pos][num]=ans; return ans; } int solve(int n) { int len=0; while(n) { a[++len]=n%10; n/=10; } return dfs(len+1,-1,true); } int main() { int n; memset(dp,-1,sizeof(dp)); while(scanf("%d",&n)!=EOF) printf("%d ",solve(n)); return 0; }
开始以为很简单,直接在前面的dfs再加一个sum记录0-9,发现0的时候出问题了。
他有前导零的情况也算计去了,比如19他会算00-09多算了10个。所以这题要在0的情况在加一个前导零的变量控制,其他1-9直接用上面的代码。0的情况只比前面多一种情况,就是前导零不加值。这里要注意的是不能再用pow()函数了,pow()只会返回int型数值,这里要用long long 的数值,所以要写一个快速幂。
代码:
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #define ll long long #define inf 0x3f3f3f3f #include<cmath> using namespace std; ll dp[25][25]; ll a[25]; ll poww(ll a,ll b)//快速幂 { ll ans=1; while(b) { if(b&1) ans*=a; a*=a; b>>=1; } return ans; } ll dfs(int pos,int num,bool limit,bool lead,int sum)//lead是否为前导零,是就是true,sum就是求0-9 { if(pos==0) return 0; ll ans=0; int k=limit?a[pos-1]:9; if(!limit&&dp[pos][num]!=-1&&!lead)//如果sum是0 ,且为前导零则不能用记忆的dp了. return dp[pos][num]; if(num==sum) { if(sum==0)//特判0的情况 { if(limit&&!lead)//不是前导零 { for(int i=pos-1;i>=1;i--) ans+=a[i]*poww(10,i-1); ans++; } else if(!limit&&!lead) ans+=poww(10,pos-1); else if(lead)//只有前导零有区别,不要加值 ans+=0; } else { if(limit) { for(int i=pos-1;i>=1;i--) ans+=a[i]*poww(10,i-1); ans++; } else ans+=poww(10,pos-1); } } for(int i=0;i<=k;i++) { ans+=dfs(pos-1,i,limit&&i==a[pos-1],lead&&i==0,sum); } if(!limit&&!lead) dp[pos][num]=ans; return ans; } ll solve(ll n,int i) { int len=0; while(n) { a[++len]=n%10; n/=10; } if(i==0) return dfs(len+1,-1,true,true,i); else return dfs(len+1,-1,true,false,i);//不是0,就不需要前导零,可以直接赋false,前面的dfs就不会用影响 } int main() { ll n,m; while(scanf("%lld%lld",&n,&m)!=EOF) { for(int i=0;i<=9;i++) { memset(dp,-1,sizeof(dp));//这里每个数要清空一次dp数组 ,因为他们代表的值不一样 printf("%lld ",solve(m,i)-solve(n-1,i)); } } return 0; }
做了几天只理解成这样,虽然在努力想讲好,但是还是很烂。。。再苦再难,也要前行!
贴个讲数位dp一个博客https://blog.csdn.net/wust_zzwh/article/details/52100392