CF924F Minimal Subset Difference (dp of dp)

CF924F Minimal Subset Difference

看到求 [l,r] 中满足条件的数,以及 l,r 的数据范围,可以联想到数位 dp:将答案拆成 [1,r][1,l1] 两部分计算。又看到里面又是一个最优化问题,猜测是一个 dp of dp 状物。

那么我们就来研究一下 f(n) 这个函数。它是一个数通过某种操作所达到的最值,那么就有两种思考方向,一种是寻找操作的贪心策略,另一种是动态规划求最值。

通过手玩一些数可以发现,不存在某种明显的贪心策略。那么怎么 dp?首先可以观察到一个性质:f(n)9。感性理解是容易的,因为可以构造出一种策略,每当你下一次加操作会超过 9 时,你都可以将这次操作改成减从而时答案始终保持在 [9,9] 之间。这样便将答案框在更小的范围内。

进一步可以发现,中间过程会达到的绝对值最大值也可以推出不超过 9+7×9=72 个。这时候我们希望能够写出一个状态,使得对于数位 dp 的终止状态构成的数 x,我都能够 O(1) 判断 f(x)k 是否正确。

于是需要设计一个可达性状态。到这里也就可以大胆将当前答案放到状态里了,设 gsta,i 表示当前状态 sta,能否达到绝对值为 i。转移实际上就是一个背包,每增加一位就转移到一个新状态 pgp,j+cgp,|jc|。发现转移就是平移,并且每一位只有两种状态,所以直接用 __int128 将每个状态压成一个数去转移。

猜测状态数不多,于是写出 g 的自动机,发现状态数不超过 104 的量级。那么就可以用 dp 套 dp,设 fdep,sta,k 表示当前考虑到的位在自动机上状态为 sta,能形成的满足条件的数字的个数。剩下的就是数位 dp 的板子了。

#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define mk std::make_pair
#define fi first
#define se second
#define pb push_back

using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
/*

*/
const int N = 21, M = 18010, B = 81;
int q, n, k, tot, len;
int tr[M][10], g[M], a[N];
std::map<__int128, int> mp;
i64 f[N][M][N];
__int128 o = 1;
void write(__int128 x) {
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) write(x / 10);
	putchar(x % 10 + '0');
	return;
}
i64 build(__int128 sta) {
	if(mp[sta]) return mp[sta];
	int id = mp[sta] = ++tot;
	for(__int128 i = 0; i <= B; i++) {
		if((sta >> i) & 1) {
			g[id] = i;
			break; 
		}
	} 
	for(int c = 0; c <= 9; c++) {
		__int128 nxt = 0;
		for(__int128 i = 0; i <= B; i++) {
			if((sta >> i) & 1) {
				if(i + c <= B) nxt |= (o << (i + c));
				if(i >= c) nxt |= (o << (i - c));
				else nxt |= (o << (c - i));
			}
		}	
		tr[id][c] = build(nxt);
	}
	return id;
}
i64 dfs(int dep, int eq, int sta) {
	if(!dep) {
		return g[sta] <= k;
	}
	if(!eq && f[dep][sta][k] != -1) return f[dep][sta][k];
	int ed = eq ? a[dep] : 9;
	i64 ret = 0;
	for(int i = 0; i <= ed; i++) {
		ret += dfs(dep - 1, eq && (i == ed), tr[sta][i]);
	}
	if(!eq) f[dep][sta][k] = ret;
	return ret;
}
i64 solve(i64 x) {
	len = 0;
	while(x) a[++len] = x % 10, x /= 10;
	return dfs(len, 1, 1);
}
void fake_main() {
	i64 l, r;
	std::cin >> l >> r >> k;

	if(k >= 9) std::cout << r - l + 1<< "\n";
	else std::cout << solve(r) - solve(l - 1) << "\n";
}
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    memset(f, -1, sizeof(f));
    build(o);
	std::cin >> q;

	while(q--) fake_main();

	return 0;
}
posted @   Fire_Raku  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示