测试地址:B-number
题目大意:给定一些整数,求以这些整数为n时,区间[0,n]中有多少既含有“13”这个子串又能被13整除的数(称为B数)。
做法:以前没写过数位DP的记忆化搜索写法,代码风格是向这位大神学习的。记忆化搜索时是从高位到低位搜索,而dp数组的计算是从低位到高位计算的,和迭代写法差不多。dfs函数有五个参数:pos,pre,mod,limit,flag,分别表示当前在第pos位(从低位到高位数),第pos+1位是pre,在这之前构成的数字(为了简便称为s)被13除余数为mod,limit表示当前位有没有被限制,flag表示s中含不含有“13”子串。设dp[i][j][k]为剩下i位数未确定,s被13除余数为j,s特性为k时可能有的B数个数,其中k=0表示s含“13”子串,k=1表示s不含“13”子串但最低位为1,k=2表示s不含“13”子串且最低位不为1。如果走到的状态无法直接用已算出的状态表示,枚举第pos位,注意限制,继续往下搜索。然后注意搜索回来的时候,如果当前位是被限制的,那么不能向dp数组中写入数据,否则根据当前状态的特性向dp数组中写入数据。详情参看代码。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,ans,bit[20];
int dp[20][20][3];
int dfs(int pos,int pre,int mod,bool limit,bool flag)
{
if (pos<=0) return flag&&(mod==0);
if (!limit&&flag&&dp[pos][mod][0]!=-1) return dp[pos][mod][0];
if (!limit&&!flag&&pre!=1&&dp[pos][mod][2]!=-1) return dp[pos][mod][2];
if (!limit&&!flag&&pre==1&&dp[pos][mod][1]!=-1) return dp[pos][mod][1];
int up=limit?bit[pos]:9,ret=0;
for(int i=0;i<=up;i++)
ret+=dfs(pos-1,i,(mod*10+i)%13,limit&&(i==up),flag||(pre==1&&i==3));
if (!limit)
{
if (flag) dp[pos][mod][0]=ret;
if (pre!=1&&!flag) dp[pos][mod][2]=ret;
if (pre==1&&!flag) dp[pos][mod][1]=ret;
}
return ret;
}
int solve(int n)
{
int len=0;
while(n)
{
bit[++len]=n%10;
n/=10;
}
return dfs(len,0,0,1,0);
}
int main()
{
memset(dp,-1,sizeof(dp));
while(scanf("%d",&n)!=EOF)
{
printf("%d\n",solve(n));
}
return 0;
}