【赛前复习】数位 DP

前言:

躺平青年, 无所畏惧!

推荐博客:

数位dp总结 之 从入门到模板

简单介绍:

数位 \(dp\) 是一种计数用的 \(dp\),一般就是要统计一个区间 \([l,r]\) 内满足一些条件数的个数。

所谓数位 \(dp\),字面意思就是在数位上进行 \(dp\)

适用于数据范围较大,\(dp\) 意味较浓,而且可以在数位上进行操作转移的问题。

题目:

Round Numbers S

\(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

\(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;
} 
posted @ 2021-10-18 16:10  Spring-Araki  阅读(54)  评论(0编辑  收藏  举报