【赛前复习】数位 DP
前言:
躺平青年, 无所畏惧!
推荐博客:
简单介绍:
数位 \(dp\) 是一种计数用的 \(dp\),一般就是要统计一个区间 \([l,r]\) 内满足一些条件数的个数。
所谓数位 \(dp\),字面意思就是在数位上进行 \(dp\)。
适用于数据范围较大,\(dp\) 意味较浓,而且可以在数位上进行操作转移的问题。
题目:
Round Numbers S
如果一个正整数的二进制表示中,\(0\) 的数目不小于 \(1\) 的数目,那么它就被称为「圆数」。求区间 \([l,r]\) 中有多少个「圆数」。
因为二进制数中 \(0/1\) 的个数有关,可以确定 \(DP\) 状态为 \(f[i][j][k]\),分别为数位、 \(0\) 的个数 、 \(1\) 的个数。
继而考虑状态转移……
代码有详细一点的解释。
题外话:这题我以前还写过题解,写得好生拉跨啊……不忍直视
点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
const int N = 40;
ll a,b,f[N][N][N];//f[i][j][k]表示共有i位,其中含有j个0和k个1的数字个数
int tot,dig[N];
ll dfs(int len,int sum0,int sum1,bool f0,bool f1) {
/*
len : 数位
sum0 : 0的个数
sum1 : 1的个数
f0 : 是否能取到最大值(1不能/0能)
f1 : 是否在前导零里面(1是/0不是)
*/
if(!len) return (f1 || sum0 >= sum1);//数位为零且满足条件,返回值为1
if(!f1 && !f0 && f[len][sum0][sum1]) return f[len][sum0][sum1];//记忆化
ll res = 0;
for(int i = 0; i <= (f0 ? dig[len] : 1); i ++) {
if(f1 && !i) res += dfs(len - 1,0,0,i == dig[len] && f0,1);//如果位是前导零,1/0个数仍是0,继续向前找
/*
此处 i == dig[len] && f0 的意思是,
除了当前位为给定数上该位的值(不能更大)而且不能取到最大值的情况,
其他情况都能取到最大值(1不能取最大值/0能)
*/
else {//不是前导零
if(!i) res += dfs(len - 1,sum0 + 1,sum1,f0 && i == dig[len],f1);
//如果当前位为 0,(数位减1(当前数位确定),0的个数加1,1不变,同上,当前f1状态未改变)
else res += dfs(len - 1,sum0,sum1 + 1,f0,0);//解释同上,转换一下
}
}
if(!f0 && !f1) f[len][sum0][sum1] = res;//记忆化
return res;
}
ll solve(ll x) {
tot = 0;
while(x) {//分解数位
dig[++tot] = x & 1;
x >>= 1;
}
return dfs(tot,0,0,1,1);
//初始状态,0/1个数均为0,当前位不能取到最大值(有限制),当前位仍是前导零(未赋值)
}
int main() {
scanf("%lld %lld",&a,&b);
printf("%lld",solve(b) - solve(a - 1));
return 0;
}
The Maths Lecture
求有多少个 \(n\) 位数的后缀可以被 \(k\) 整除,答案对 \(m\) 取模。
定义 \(dp[i][j][0/1]\) 表示 到第 \(i\) 位时 模 \(k\) 余 \(j\), 后缀中是否存在 \(k\) 的倍数
那显然就是从后往前推了。
还挺巧妙的 。。
点击查看代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define int long long
const int N = 1005;
int n, m, k, dp[N][N][2], p[N];
int dfs(int len , int sum, bool tag) {
if(len == n + 1) return tag;
if(~dp[len][sum][tag]) return dp[len][sum][tag];
int res = 0, mx = len == n;
for(int i = mx; i <= 9; i ++) {
res = (res + dfs(len + 1, (sum + i * p[len]) % k, tag || ((sum + i * p[len]) % k == 0 && (sum || i)))) % m;
}
dp[len][sum][tag] = res;
return res;
}
signed main() {
scanf("%lld %lld %lld", &n, &k, &m);
memset(dp, -1, sizeof(dp));
p[1] = 1;
for(int i = 2; i <= n; i ++) p[i] = p[i - 1] * 10 % k;
printf("%lld", dfs(1, 0, 0));
return 0;
}