题目:
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
题意:给你一个数字n,让你求1~n中满足题目要求的数的个数,x满足题目要求当且仅当x的十进制表示中含有子串’13‘以及x是13的倍数。
我们现在来分析一下这道题目,如果题目只要求含有子串’13‘的数目的话,那与之前的那个Bomb题基本上是一模一样了,现在这道题不仅要求含有子串’13‘,还要去是13的倍数,我们数位dp是处理的单个位,那我们如何判断他是不是13的倍数呢?先来分析这样一个问题,我们如何能够把几个单独的数字连成一个数呢?
比如 1 3 5 2 1 是5个单独的数字,我们如何把他变成13521这个5位数呢?我们实际上是一位一位处理的,先用1乘10再加上3得到13,然后用13乘10再加上5得到135,然后用135乘10再加上2得到1352,最后用1352乘10再加上1得到13521,
明白了这个原理之后,我还要给大家再说一个性质:将取模运算混入加法减法以及乘法组成的算式中是不会影响最后结果的正确性的,比如
(a+b)%n=((a%n)+(b%n))%n
a*b%n=((a%n)*(b%n))%n
但需要注意的是取模运算不能直接适用于除法运算,如果要想在取模运算中加入除法就必须要涉及到求逆元了,在这里我就不详细介绍了,在之后我们会单独写一篇博客来介绍如何求逆元。
知道了这个性质,我们就可以用一个re来记录到当前这位之前的数对13取余的值,所以这个状态也能够得到很好的表示了,剩下的就是一些细节性的问题了,我会在代码中做出详细介绍。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
typedef long long ll;
int a[300];
ll dp[30][20][20][2];
ll dfs(int pos,int pre,int re,int flag1,int flag2)//re用于记录到第pos-1位为止的数对13取余的值
{
if(pos==0)
{
if(re%13==0) return flag1;//函数递归出口
return 0;
}
if(dp[pos][pre][re][flag1]!=-1&&flag2)//只有保证dp数组被更新过以及当前位的数字可以任选才能够使用记忆化数组储存的值
return dp[pos][pre][re][flag1];
ll ans=0;//记录第pos位取不同值时的方案数之和,更新dp数组
int x=flag2?9:a[pos];//确定当前可以枚举的上限
for(int i=0;i<=x;i++)
ans+=dfs(pos-1,i,(re*10+i)%13,(pre==1&&i==3)||flag1,i<x||flag2);//枚举下一位
//如果在某次函数调用后flag1变为1,说明到当前位置已经满足题目中所给条件,则之后一定会一直满足条件
//如果在某次函数调用后flag2变为1,说明到当前位置已经可以任选,则之后一定可以任选(比如两个数字比大小,当一个数字的高位大于另一个数字,则不管低位是什么,高位大的一定大)
if(flag2) dp[pos][pre][re][flag1]=ans;//只有保证当前位置任选才能够更新记忆化数组储存的值
return ans;
}
ll solve(ll x)
{
int tt=0;
memset(dp,-1,sizeof dp);//每进行一次函数调用都要初始化一次
do
{
a[++tt]=x%10;
x/=10;
}while(x);
return dfs(tt,0,0,0,0);//函数调用,从高位往低位遍历
}
int main()
{
ll n;
while(scanf("%lld",&n)!=EOF)
{
printf("%lld\n",solve(n));
}
return 0;
}
当要判断一个数是不是某某的倍数时,就可以用这样的数位dp来做。对于不同的题目我们只是更改一下搜索时传入的参数而已,本质还是差不多的。
如果大家对这道题还有什么疑问的话,欢迎在评论区里提问!