[一本通提高数位动态规划]数字游戏:取模数题解
1前言
本文为数字游戏:取模数的题解
需要读者对数位dp有基础的了解,建议先阅读
论数位dp–胎教级教学
B3883 [信息与未来 2015] 求回文数 数位dp题解
论进制类型的数位dp:胎教级教学
2问题
题目描述
定义一种取模数,满足各位数字之和 m o d N mod N modN为 0 0 0,指定一个整数闭区间 [ a , b ] [a,b] [a,b],问这个区间内有多少个取模数。
输入格式
题目有多组测试数据。每组只含 3 3 3个数字 a , b , N ( 1 < = a , b < = 2 31 , 1 < = n < 100 ) a, b, N (1 <= a, b <= 2^{31},1 <= n < 100) a,b,N(1<=a,b<=231,1<=n<100)。
输出格式
每个测试用例输出一行,表示各位数字和 m o d N mod N modN为 0 0 0 的数的个数。
看这个 a , b a,b a,b的范围,直接可以确定是数位dp,但是该怎么dp?
3状态的设置
我们发现,假设现在 n = 9 n = 9 n=9,那么我们在一个取模数的最高位前面加上一位 9 9 9,这个数依旧是取模数
在一个各位数字和 m o d 9 = 1 mod 9 = 1 mod9=1的情况下,在最前面插入一个 8 8 8,也产生了取模数
我们可以把不同位数的的取模数个数看成子问题,子问题可以转化为更大的问题(方式如上),这就满足的dp的条件
有了子问题和可转移的性质,我们可以设 d p i , j , k dp_{i,j,k} dpi,j,k为 i i i位 j j j开头,
各位之和 m o d n = k mod n = k modn=k的数的个数
属性显然为COUNT(加上子问题得到当前状态)
4数位dp-part1预处理
在 n n n固定的情况下, d p dp dp数组也是固定的,与 a , b a,b a,b无关
所以我们可以预处理 d p dp dp
首先,对于所有 d p i , j , k dp_{i,j,k} dpi,j,k, i = 1 i = 1 i=1的情况下,如果 k = j m o d n k = j mod n k=jmodn
则 d p i , j , k = 1 dp_{i,j,k} = 1 dpi,j,k=1,否则 d p i , j , k = 0 dp_{i,j,k} = 0 dpi,j,k=0
接下来就可以枚举 i , j , k i,j,k i,j,k,此外再枚举 l l l为上一位数字的情况
利用模运算转移,得状态转移方程
d p i , j , k = d p i , j , k + d p i − 1 , l , m o d s ( k − j ) dp_{i,j,k} =dp_{i,j,k}+dp_{i-1,l,mods(k-j)} dpi,j,k=dpi,j,k+dpi−1,l,mods(k−j)
其中 m o d s ( k − j ) mods(k-j) mods(k−j)等价于 ( ( k − j ) m o d n + n ) m o d n ((k-j)modn+n) mod n ((k−j)modn+n)modn,这样可以防止出现负数
5数位dp-part2利用状态求解
我们首先考虑一个数 23456 23456 23456
可以将其分解为 0 − 19999 0-19999 0−19999和 20000 − 23456 20000-23456 20000−23456两个区间
0 − 19999 0-19999 0−19999我们预处理过了,方案数等于
∑ d p 5 , j , 0 \sum dp_{5,j,0} ∑dp5,j,0对于此处情况 0 < = j < = 1 0<=j<=1 0<=j<=1
普遍的,此处的 0 < = j < = s i 0<=j<=s_{i} 0<=j<=si, s i s_{i} si为原数 i i i位
然后呢,我们可以把 3456 3456 3456看做子问题
第一位必然为 2 2 2,不为 2 2 2的情况已经得出
这样的话我们就可以打个标记,将已经固定的位数求和
但是我们这样一直缩小问题规模,会产生一个问题,会忽略边界值
这个也好办,我们特判最后标记变量如果被 n n n整除,就可以取到边界,答案 + 1 +1 +1
算法完成了,但是看到这的你可能会有一些问题,我来解答
1.为什么不处理前导 0 0 0
答:前导 0 0 0是合法的,因为处理方式是加和,所以 0 0 0相当于空位
2.如果左边界为 1 1 1怎么办, 1 − 1 1-1 1−1之后传到函数里的参数为 0 0 0
答:特判, 0 0 0本身合法,返回 1 1 1
6代码
有了如上思想,你就可以写出代码
附作者的代码(c++)
#include<bits/stdc++.h> using namespace std; long long a,b,n; long long dp[20][20][200]; int mods(int x){ return (x%n+n)%n; } void init(){ memset(dp,0,sizeof(dp)); for(int i = 0;i<=9;i++){ dp[1][i][i%n]++; } for(int i = 2;i<=12;i++){ for(int j = 0;j<=9;j++){ for(int k = 0;k<n;k++){ for(int l = 0;l<=9;l++){ dp[i][j][k]+=dp[i-1][l][mods(k-j)]; } } } } } int solve(int x){ if(x==0){ return 1; } int h = x,s[1145],idx = 0,ans = 0,res = 0; while(h){ s[++idx] = h%10; h/=10; } for(int i = idx;i>=1;i--){ for(int j = 0;j<s[i];j++){ ans+=dp[i][j][mods(n-res)]; } res+=s[i]; res%=n; if(i==1&&res%n==0){ ans++; } } return ans; } int main(){ while(cin>>a>>b>>n){ init(); int ans = solve(b)-solve(a-1); cout<<ans<<endl; } return 0; }
7后记
本题很好地展现了数位dp的精髓
预处理部分情况+分解子问题+特判
本文作者是蒟蒻,如有错误请各位神犇指点
森林古猿出品,必属精品,请认准CSDN森林古猿1!