「学习笔记」数位 DP

这篇具有很好参考价值的文章主要介绍了「学习笔记」数位 DP。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

「学习笔记」数位 DP

意义不大的题不写了。

点击查看目录
目录
  • 「学习笔记」数位 DP
    • 概述
    • 例题
      • P2657 [SCOI2009] windy 数
        • 思路
        • 代码
      • P4317 花神的数论题
        • 思路
      • P4124 [CQOI2016]手机号码
        • 思路
        • 代码
      • haha数
        • 题意
        • 思路
        • 代码
      • 0和1的熟练
        • 题意
        • 思路
        • 代码
      • 苍与红的试炼
        • 题意
        • 思路
        • 代码

概述

数位 DP 一般用来解决「在一个较大的区间内统计具有一定特征的数的数量」的问题。

数位 DP 一般有两种做法:

  • 递推法:首先需要预处理出具有一定条件的数的个数,然后将上限按数位拆分开来考虑贡献。
  • 暴搜法:直接记忆化搜索具有特定条件的数的个数。

例题

P2657 [SCOI2009] windy 数

思路

本题使用递推。

\(f_{i,j}\) 表示最高位为 \(j\)\(i\) 位 windy 数数量(\(j\) 可以为 \(0\)),显然:

\[f_{i, j} = \begin{cases} 1 &i=1\\ \sum_{k=0}^{9}[\left|j - k\right| \ge 2]f_{i - 1, k} &\text{otherwise} \end{cases} \]

然后计算答案,把上限 \(x\) 的每一位拆开(假设 \(x\)\(l\) 位),考虑到第 \(i\) 位时,前 \(l-i\) 位与 \(x\) 相同,后 \(i\) 位的方案数用 \(f_{i,j}\) 计算。

但是有一个问题:\(0001\) 去除前导零后合法,但是 \(10001\) 不合法,因此为了保证之后计算的数依然合法,\(f_{4, 0}\) 不会记录像 \(0001\) 这样去除前导零后合法但在最高位再加上一个数后不合法的数的数量。

此时我们需要一个辅助数组 \(g_{i}\),即被漏算了的首位为 \(0\)\(i\) 位数的数量。当第 \(i-1\) 位大于等于 \(2\) 时,首位为 \(0\)\(i\) 位数不会被漏算(因为此时第 \(i\) 位和第 \(i-1\) 差值大于等于为 \(2\),仍然合法),但是第 \(i-1\) 位小于 \(2\) 的数会被漏算,所以易得:

\[g_{i} = g_{i - 1} + f_{i - 1, 0} + f_{i - 1, 1} \]

计算答案时加上即可。

代码

点击查看代码
const ll N = 15, inf = 1ll << 40, P = 1e9 + 7;
namespace SOLVE {
	ll a, b, f[N][N], g[N];
	inline ll rnt () {
		ll x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}

	inline ll GetAns (ll x) {
		ll len = 0, num[N] = {0}, sum = 0;
		while (x) num[++len] = x % 10, x /= 10;
		for_ (i, len, 1) {
			if (i == len) {
				_for (j, 0, num[i] - 1) sum += f[i][j];
				sum += g[i];
				continue;
			}
			_for (j, 0, num[i] - 1) {
				if (abs (j - num[i + 1]) < 2) continue;
				sum += f[i][j];
			}
			if (abs (num[i] - num[i + 1]) < 2) break;
		}
		return sum;
	}

	inline void In () {
		a = rnt (), b = rnt ();
		return;
	}
	inline void Solve () {
		_for (i, 0, 9) f[1][i] = 1;
		g[1] = 2;
		_for (i, 2, 10) {
			g[i] = g[i - 1] + f[i - 1][0] + f[i - 1][1];
			_for (j, 0, 9) {
				_for (k, 0, 9) {
					if (abs (j - k) < 2) continue;
					f[i][j] += f[i - 1][k];
				}
			}
		}
		return;
	}
	inline void Out () {
		printf ("%lld\n", GetAns (b + 1) - GetAns (a));
		return;
	}
}

P4317 花神的数论题

思路

使用递推。

\(f_{i, j, k}\) 表示有 \(i\) 位,最高位为 \(j(j\in\{0,1\})\),有 \(k\)\(1\)二进制数。显然:

\[\begin{aligned} f_{i, 0, j} &= \begin{cases} 1 &j=0\\ f_{i - 1, 0, j} + f_{i - 1, 1, j} &\text{otherwise} \end{cases}\\ f_{i, 1, j} &= \begin{cases} 1 &i=1\\ f_{i - 1, 0, j - 1} + f_{i - 1, 1, j - 1} &\text{otherwise} \end{cases} \end{aligned} \]

然后由于要算乘积,要使用快速幂计算有 \(k\)\(1\) 的二进制数的乘积总和。

P4124 [CQOI2016]手机号码

思路

使用暴搜。

定义函数:

ll Dfs (ll p, ll a, ll b, ll sm, ll fr, ll et, ll li)

其中,\(p\) 表示第几位,\(a\) 表示第 \(p\) 位的数,\(b\) 表示第 \(p+1\) 位的数,\(sm\) 表示是否已经存在一样的连续三个数,\(fr\) 是否出现过 \(4\)\(et\) 是否出现过 \(9\)\(li\) 前面几位是否和上限完全一样(这个会影响搜索时下一位的的数,如果完全一样则下一位的的数不能超过上限的这一位,否则可以到 \(9\))。

代码

点击查看代码
ll Dfs (ll p, ll a, ll b, ll sm, ll fr, ll et, ll li) {
	if (fr && et) return 0;
	if (!p) return sm;
	if (!li && f[p][a][b][sm][fr][et] > 0) return f[p][a][b][sm][fr][et];
	ll sum = 0, mx = li ? t[p] : 9;
	_for (i, 0, mx) sum += Dfs (p - 1, i, a, sm || (i == a && i == b), fr || (i == 4), et || (i == 8), li && i == mx);
	if (!li) f[p][a][b][sm][fr][et] = sum;
	return sum;
}
inline ll GetAns (ll x) {
	if (x < 1e10) return 0;
	ll len = 0, sum = 0;
	while (x) t[++len] = x % 10, x /= 10;
	memset (f, -1, sizeof (f));
	_for (i, 1, t[len]) sum += Dfs (len - 1, i, 0, 0, i == 4, i == 8, i == t[len]);
	return sum;
}

haha数

题意

一个正整数,当且仅当在十进制表示下,它的各位非零数字能整除该数本身时(即它的所有非零位的数字都是该数的约数),被称作 haha 数。给定 \(l\)\(r\),求在 \([l, r]\) 范围内 haha 数的个数。

\(1\le l \le r \le9\times10^{18}\)

思路

使用暴搜。

定义函数:

ll Dfs (ll p, ll md, ll sta, ll li)

其中,\(p\) 表示第几位,\(md\) 表示当前的数膜 \(\operatorname{lcm}(1,2,3,4,5,6,7,8,9)=2520\) 的余数,\(sta\) 表示 \(2\sim8\) 的数是否出现过的状态,\(li\) 前面几位是否和上限完全一样。

最后判断合法时,看 \(md\) 是否是 \(sta\) 记录的所有出现过的数的倍数即可。

代码

点击查看代码
inline ll w (ll x) { return (x < 2) ? 0 : (1 << (x - 2)); }
inline bool Check (ll md, ll sta) {
	_for (i, 2, 9) if ((sta & w (i)) && (md % i)) return 0;
	return 1;
}

ll Dfs (ll p, ll md, ll sta, ll li) {
	if (!p) return Check (md, sta);
	if (!li && f[p][md][sta] >= 0) return f[p][md][sta];
	ll sum = 0, mx = li ? t[p] : 9;
	_for (i, 0, mx) sum += Dfs (p - 1, (md * 10 + i) % 2520, sta | w (i), li & (i == mx));
	if (!li) f[p][md][sta] = sum;
	return sum;
}

0和1的熟练

题意

有一个程序员,他使用只有两个按键的键盘打字。这两个按键就是 \(0\)\(1\),只有达到高端境界的人才能出入此境。记住,只有从 \(l\)\(r\) 的所有数都手打过一遍了才能练就如此功夫。作为真正的高手,你一定能快速地回答区间 \([l,r]\) 中有多少数字的二进制表示(不含前导零)中 \(0\) 的个数不少于 \(1\) 的个数,因为你每天都进行一遍这个练习。

\(1\le l<r\le2\times10^9\)

思路

使用暴搜。

定义函数:

ll Dfs (ll p, ll c1, ll len, ll li)

其中,\(p\) 表示第几位,\(c1\) 表示当前的数有几个 \(1\)\(len\) 当前的数变为二进制后有多少位,\(li\) 前面几位是否和上限完全一样。

代码

点击查看代码
ll Dfs (ll p, ll c1, ll len, ll li) {
	if (!p) return len ? (len - c1 >= c1) : 1;
	if (!li && f[p][c1][len] >= 0) return f[p][c1][len];
	ll sum = 0, mx = li ? t[p] : 1;
	_for (i, 0, mx) sum += Dfs (p - 1, c1 + (i == 1), len + (len || i), li & (i == mx));
	if (!li) f[p][c1][len] = sum;
	return sum;
}

苍与红的试炼

题意

辉煌的时代终将落幕吗,只有时间的车轮滚滚向前。

来自苍空之上的,是点亮前路的希望,是划破阴云的曙光。

为协助天城追回那被铅色未来蒙蔽前路之人,你需要完成天城的试炼以取得天城的认可。

漫樱飞舞的古城中,有一些标有数字 \(0\sim9\) 的御守。现在天城给了你一个正整数 \(d\),你需要按照某种顺序排列一些御守,写下由这些御守拼成的数字 \(x\),并保证 \(x\) 能被 \(d\) 整除。

然而想通过「重樱之鬼谋」的考验哪有这么简单。天城还指定了一个正整数 \(s\),你必须保证你所选择的所有御守上的数字之和等于 \(s\) 才能满足要求。同时为了检验你的能力,天城要求你给出满足要求的最小的 \(x\)。当然如果不存在满足要求的 \(x\),你也必须要向天城指出这一点。

跨越时间之长河,斩断命运之枷锁。挑战者啊,穷尽武略与智谋,迎接来自顶点的试炼吧。

\(1\le d\le500, 1\le s\le 5000\)

思路

没有上限,所以直接深搜会爆掉。

那么考虑广搜,放入队列一个数对 \((a, b)\),表示当前数膜 \(d\) 等于 \(a\),每一位数之和等于 \(b\)。显然当 \(a=0,b=s\) 时搜索结束。

状态只有 \(500\times5000=2500000\) 种,记忆化一下即可。文章来源地址https://www.toymoban.com/news/detail-407864.html

代码

点击查看代码
namespace SOLVE {
	const ll N = 1e7;
	ll d, s, f[N], g[N], jl[N], ans;
	inline ll rnt () {
		ll x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}
	void Print (ll p) {
		if (p == 1) return;
		Print (g[p]);
		printf ("%lld", f[p]);
	}
	inline void In () {
		d = rnt (), s = rnt ();
		return;
	}
	inline void Solve () {
		std::queue <ll> q;
		q.push (0);
		jl[0] = 1;
		ll h = 0, t = 1;
		while (!q.empty ()) {
			ll fr = q.front ();
			++h, q.pop ();
			_for (i, 0, 9) {
				ll dd = (fr % 501 * 10 + i) % d;
				ll ss = fr / 501 + i;
				ll num = dd + ss * 501;
				if (ss > s) continue;
				if (jl[num]) continue;
				f[++t] = i, g[t] = h;
				if (!dd && ss == s) {
					ans = t;
					return;
				}
				jl[num] = 1;
				q.push (num);
			}
		}
		return;
	}
	inline void Out () {
		if (ans) Print (ans), puts ("");
		else puts ("-1");
		return;
	}
}

到了这里,关于「学习笔记」数位 DP的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数位DP万能模板

    ☆* o(≧▽≦)o *☆嗨~我是小奥🍹 📄📄📄个人博客:小奥的博客 📄📄📄CSDN:个人CSDN 📙📙📙Github:传送门 📅📅📅面经分享(牛客主页):传送门 🍹文章作者技术和水平有限,如果文中出现错误,希望大家多多指正! 📜 如果觉得内容还不错,欢迎点赞收藏关注哟!

    2024年01月17日
    浏览(30)
  • LeetCode---121双周赛---数位dp

    2996. 大于等于顺序前缀和的最小缺失整数 2997. 使数组异或和等于 K 的最少操作次数 2998. 使 X 和 Y 相等的最少操作次数 2999. 统计强大整数的数目 简单的模拟题,只要按照题目的要求去写代码即可,代码如下 这题考异或的性质---相同为0,相异为1,我们只要关心nums的异或和与

    2024年01月22日
    浏览(41)
  • 异或三角形(数位dp)

    [Link](异或三角 - 蓝桥云课 (lanqiao.cn)) 参考:2021蓝桥杯国赛-J异或三角形-数位dp_塔子哥来了的博客-CSDN博客_蓝桥杯数位dp 给定 T T T 个数 n 1 , n 2 , . . . , n T n_1,n_2,...,n_T n 1 ​ , n 2 ​ , . . . , n T ​ ,对每个 n i n_i n i ​ 请求出有多少组 a , b , c a,b,c a , b , c 满足: 1 ≤ a , b , c ≤

    2024年02月09日
    浏览(32)
  • 周赛348(模拟、反向思维、数位DP)

    难度中等3 给你一个下标从 0 开始的字符串 s ,重复执行下述操作 任意 次: 在字符串中选出一个下标 i ,并使 c 为字符串下标 i 处的字符。并在 i 左侧 (如果有)和 右侧 (如果有)各 删除 一个距离 i 最近 的字符 c 。 请你通过执行上述操作任意次,使 s 的长度 最小化 。

    2024年02月07日
    浏览(63)
  • 「数位dp」统计整数数目(力扣第2719题)

    本题为1月16日力扣每日一题 题目来源:力扣第2719题 题目tag: 数位dp 动态规划 给你两个数字字符串num1和num2,以及两个整数max_sum和min_sum。如果一个整数x满足以下条件,我们称它是一个好整数: (num1 leq x leq num2) (min_sum leq digit_sum(x) leq max_sum) 请你返回好整数的数目。答案

    2024年01月16日
    浏览(32)
  • C++ 动态规划 数位统计DP 计数问题

    给定两个整数 a 和 b ,求 a 和 b 之间的所有数字中 0∼9 的出现次数。 例如,a=1024,b=1032 ,则 a 和 b 之间共有 9 个数如下: 1024 1025 1026 1027 1028 1029 1030 1031 1032 其中 0 出现 10 次,1 出现 10 次,2 出现 7 次,3 出现 3 次等等… 输入格式 输入包含多组测试数据。 每组测试数据占一

    2024年02月20日
    浏览(31)
  • 算法竞赛备赛进阶之数位DP训练

    数位DP的思想就是对每一位进行DP,计算时记忆化每一位可以有的状态,其作用是减少运算时间,避免重复计算。 数位DP是一种计数用的DP,一般就是要统计一个区间[A,B]内满足一些条件数的个数。 以1e9甚至1e18、1e100的问题为例,因为在统计情况下有很多重复的计算,数位DP实

    2024年01月16日
    浏览(34)
  • LeetCode 2719. 统计整数数目,数位dp板子题

    1、题目描述 给你两个数字字符串  num1  和  num2  ,以及两个整数  max_sum  和  min_sum  。如果一个整数  x  满足以下条件,我们称它是一个好整数: num1 = x = num2 min_sum = digit_sum(x) = max_sum . 请你返回好整数的数目。答案可能很大,请返回答案对  109 + 7  取余后的结果。 注意

    2024年01月17日
    浏览(35)
  • acwing算法基础之动态规划--数位统计DP、状态压缩DP、树形DP和记忆化搜索

    暂无。。。 暂无。。。 题目1 :求a~b中数字0、数字1、…、数字9出现的次数。 思路:先计算1~a中每位数字出现的次数,然后计算1~b-1中每位数字出现的次数,两个相减即是最终答案。 那么,如何计算1~a中每位数字出现的次数呢? 首先,将a的每一位存入向量num中,例如a=123

    2024年02月04日
    浏览(38)
  • 【动态规划】【 数位dp】2827. 范围中美丽整数的数目

    数位dp 动态规划汇总 给你正整数 low ,high 和 k 。 如果一个数满足以下两个条件,那么它是 美丽的 : 偶数数位的数目与奇数数位的数目相同。 这个整数可以被 k 整除。 请你返回范围 [low, high] 中美丽整数的数目。 示例 1: 输入:low = 10, high = 20, k = 3 输出:2 解释:给定范围

    2024年04月12日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包