B-number
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Problem Description
A wqb-number, or B-number for short, is a non-negative integer whose decimal form contains the sub- string "13" and can be divided by 13. For example, 130 and 2613 are wqb-numbers, but 143 and 2639 are not. Your task is to calculate how many wqb-numbers from 1 to n for a given integer n.
Input
Process till EOF. In each line, there is one positive integer n(1 <= n <= 1000000000).
Output
Print each answer in a single line.
Sample Input
13 100 200 1000
Sample Output
1 1 2 2
题目大意:求区间[1,n]中含13且是13的倍数的数字个数?
方法一:DP(循环)
感觉入错坑了...非递归的数位DP好难写(虽然一次AC了),但是一看别人的递归数位DP,非常简洁,还不容易出错,需要赶快换一下
本题比原先做的又多了一个限制:数还要满足是13的倍数,然后就又不会了...
看了题解的设置状态的部分,瞬间明白了,可以再加一维表示余数的状态,然后利用模运算的性质就能顺利成章地想到以下这种转移状态(又看到有非递归的题解,感觉还是写的麻烦了)
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n,bit[35],len,ans;
int dp[35][13][3];//dp[i][j][0]表示长度为i,模13后为j且不含13的数字个数;dp[i][j][1]表示长度为1,模13后为j且不含13,第i位为3的数字个数;dp[i][j][2]表示长度为i,模13后为j且含有13的数字个数
void Init() {
memset(dp,0,sizeof(dp));
dp[0][0][0]=1;
for(int i=1,hig=1;i<=31;++i,hig*=10) {//枚举第i位,权值为hig
for(int num=0;num<=9;++num) {//枚举第i位的数字
for(int rem=0;rem<13;++rem) {//枚举低i-1位模13的值
int cur=(num*hig+rem)%13;//cur表示第i位为num,低i-1位模13为rem时,低i位模13的值
dp[i][cur][0]+=dp[i-1][rem][0];
if(num==1) {//如果第i位为1,则需要减去第i-1位为3的情况
dp[i][cur][0]-=dp[i-1][rem][1];
}
if(num==3) {//如果第i位为3,则低i-1位中所有不含13的状态都符合
dp[i][cur][1]+=dp[i-1][rem][0];
}
dp[i][cur][2]+=dp[i-1][rem][2];
if(num==1) {//如果第i位为1,且第i-1位为3,则也符合当前状态
dp[i][cur][2]+=dp[i-1][rem][1];
}
}
}
}
}
int getCnt(int x) {//返回区间[1,x]中含13且是13的倍数的数字个数
++x;
int hx=0,tmp;//hx表示从i+1位开始的高位的值(不含低i位的权值);tmp表示从i位开始的高位的值(含第i-1位的权值)
int weight=1;//weight表示第i位的权值
bool flag=false;
ans=len=0;
while(x>0) {
bit[++len]=x%10;
x/=10;
weight*=10;
}
bit[len+1]=-1;
weight/=10;
for(int i=len;i>=1;--i,weight/=10) {//从高到低枚举每一位
for(int num=0;num<bit[i];++num) {//枚举第i位可取到的数字(该位后面的位的所有状态均能取到)
tmp=(hx*10+num)*weight;
for(int rem=0;rem<13;++rem) {//枚举低i-1位模13后的值
if((tmp+rem)%13==0) {//当前状态下,所有是13的倍数
ans+=dp[i-1][rem][2];//这些数中低i-1位中含有13的数
if(flag) {//如果高位已经出现过13
ans+=dp[i-1][rem][0];//这些数中低i-1位中不含13的数
}
else {//如果高位中没出现过13
if(bit[i+1]==1&&num==3) {//如果第i+1位为1,且第i位取3时,低i-1位可取不含13的状态
ans+=dp[i-1][rem][0];
}
if(num==1) {//如果当前位可取的数大于1,低i-1位中可取第i-1为3但不含13的状态
ans+=dp[i-1][rem][1];
}
}
}
}
}
if(bit[i+1]==1&&bit[i]==3) {//如果第i+1位为1且第i位为3,则高位中必定含有13
flag=true;
}
hx=hx*10+bit[i];
}
return ans;
}
int main() {
Init();
while(1==scanf("%d",&n)) {
printf("%d\n",getCnt(n));
}
return 0;
}
方法二:DP(递归)
递归太好写了,需要考虑的限制非常少,决定以后就专弄递归的方法吧
#include <cstdio>
#include <cstring>
using namespace std;
int bit[35],len;
int dp[35][13][3];//dp[i][j][0]表示长度为i,模13后为j且不含13的数字个数;dp[i][j][1]表示长度为1,高位的数模13后为j且不含13,第i位为3的数字个数;dp[i][j][2]表示长度为i,模13后为j且含有13的数字个数
int dfs(int pos,int pre,int rem,bool limit,bool flag) {//pos表示当前考虑的位置,pre前一位的数,rem高位的数的模13的值,limit当前位取的数是否有限制,flag前面是否出现过13
if(pos<=0) {
return flag&&rem==0?1:0;//如果前面出现过13,且余数为0,则符合题意,为1;否则不符合题意,为0
}
if(!limit) {//如果没有限制,就可以直接统计后面的所有位的情况
if(flag) {//如果前面已经出现过13,则后面所有的位任取不含13的
if(dp[pos][rem][0]!=-1) {//如果计算过就直接返回
return dp[pos][rem][0];
}
}
else {//如果前面没出现过13
if(pre==1&&dp[pos][rem][1]!=-1) {//如果上一位是1,则后面的最高位取3,若计算过就直接返回
return dp[pos][rem][1];
}
if(pre!=1&&dp[pos][rem][2]!=-1) {//如果上一位不是1,则后面取含13的数,若计算过就直接返回
return dp[pos][rem][2];
}
}
}
int res=0,mx=limit?bit[pos]:9;//mx表示当前位最大能取的数
for(int i=0;i<=mx;++i) {//枚举当前位的数字
res+=dfs(pos-1,i,(rem*10+i)%13,limit&&i==mx,flag||(pre==1&&i==3));
}
if(!limit) {//此时保存刚才算得的结果
if(flag) {
dp[pos][rem][0]=res;
}
else {
if(pre==1) {
dp[pos][rem][1]=res;
}
else {
dp[pos][rem][2]=res;
}
}
}
return res;
}
int getCnt(int n) {
len=0;
while(n>0) {
bit[++len]=n%10;
n/=10;
}
return dfs(len,0,0,true,false);
}
int main() {
int n=0;
memset(dp,-1,sizeof(dp));
while(1==scanf("%d",&n)) {
printf("%d\n",getCnt(n));
}
return 0;
}