首页 > 其他分享 >「学习笔记」数位 DP

「学习笔记」数位 DP

时间:2023-04-09 11:34:49浏览次数:45  
标签:fr return sum 笔记 li len ll DP 数位

「学习笔记」数位 DP

意义不大的题不写了。

点击查看目录

目录

概述

数位 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\) 种,记忆化一下即可。

代码

点击查看代码
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;
	}
}

标签:fr,return,sum,笔记,li,len,ll,DP,数位
From: https://www.cnblogs.com/Keven-He/p/NumberDP.html

相关文章

  • Android学习笔记(五二):服务Service(中)- 继承Service类
    通过IntentService的继承类实现命令触发的服务,也可以直接通过Service的继承类来实现。在IntentService中的例子,我们增加了StopService()的方式,用于试验。在实际应用中,IntentService常用于一次性运行,自动结束的情况,不需要人工停止干预。对于需要人工干预的停止的,长时间(或无限制)运行......
  • MYSQL 笔记
    连接数据库shell>mysql-hhost-uuser-pEnterpassword:断开数据库mysql>QUIT查询版本号和当前日期mysql>SELECTVERSION(),CURRENT_DATE;将mysql用作一个简单的计算器:mysql>SELECTSIN(PI()/4),(4+1)*5;MYSQL提示符含义|提示符|含义|mysql>准备好接受新......
  • 20230401数位DP
    数位DP数位DP通常指在\([l,r]\)区间中有多少个满足条件\(k\)的个树常见的数据范围都很大也就是说,把数字的枚举,变成数字的构造不要把数字看作是\(10^{18}\)而把数字看作是\(18\)位数的填数过程就是把原本枚举的问题转化为了构造的问题然而数位dp常通过记忆化搜索实现tips:......
  • MySQL笔记之Checkpoint机制
    CheckPoint是MySQL的WAL和Redolog的一个优化技术。 一、Checkpoint机制CheckPoint做了什么事情?将缓存池中的脏页刷回磁盘。checkpoint定期将dbbuffer的内容刷新到datafile,当遇到内存不足、dbbuffer已满等情况时,需要将dbbuffer中的内容/部分内容(特别是脏数据)转储到datafi......
  • Unity框架:JKFrame2.0学习笔记(二)——Singleton单例模式
    Singleton单例模式的基类,不用mono的类可以直接继承源码namespaceJKFrame{///<summary>///单例模式的基类///</summary>publicabstractclassSingleton<T>whereT:Singleton<T>,new(){privatestaticTinstance;public......
  • Typora入门笔记-2023-04-08
    Typora入门笔记-2023.4.081-6个#号代表标题的大小,井号越多标题越小字体holleworld!hello,worldHELLO,WORLDHELLO,WORLD引用选择狂神说Java,走向人生巅峰选择狂神说Java,走向人生巅峰选择狂神说Java,走向人生巅峰图片超链接点击跳转到狂神博客列表ABCAB......
  • Vins-Mono 阅读笔记——estimator
    vins_estimator概述基本上VINS里面绝大部分功能都在这个package下面,包括IMU数据的处理(前端),初始化(我觉得可能属于是前端),滑动窗口(后端),非线性优化(后端),关键帧的选取(部分内容)(前端)。我第一次看的时候,总是抱有一个疑问,就是为什么把这么多内容全都放在这一个node里面。为了......
  • Vins-Mono 阅读笔记——前端
    1、前端流程概述VINS-Mono的前端整个封装成了一个ROS节点其订阅的topic是:相机或者数据集发来的图片其发布topic是:由pub_img发布的"feature",发布的是当前帧的特征点,特征点分装成了sensor_msgs::PointCloudPtr类型,里面包括了每个特征点的(1)归一化平面上的坐标(2)归一化平面上的......
  • Django笔记十九之manager用法介绍
    本文首发于微信公众号:Hunter后端原文链接:Django笔记十九之manager用法介绍首先介绍一下manager的使用场景,比如我们有一些表级别的,需要重复使用的功能,都可以使用manager来实现。比如我们在前面的笔记中介绍的model的create()、update()等方法,Blog.objects.create()中......
  • CS231N assignment 1 _ softmax 学习笔记 & 解析
    [注意:考虑到这个和SVM重复很多,所以会一笔带过/省略一些]softmax和SVM只是线性分类器分类结果的评判不同,完全依靠打分最大来评判结果,误差就是希望结果尽可能接近正确分类值远大于其他值.我们将打分结果按照指数权重正则化为和为1的向量:而这个值希望尽可能接近1,也就是-l......