[AHOI2022] 山河重整 解题报告

T3,一个不错的数学题,给了不少的暴力分。

Statement

求有多少值域为 [1,n] 的集合,01背包可以凑出 1n 中的所有数字。

Subtask 16

我们从小到大选择每一个数,不难发现凑出来的数字一定是 [1,n] 的一段前缀。
于是考虑 dp,记 fi,j 表示选择完了前 i 个数,可以表示出 [1,j] 中的所有数。
如何转移?考虑当前加 i+1,新扩展的可凑区间为 [i+1,i+j+1]。于是,如果要让这段区间造成贡献必须满足 j+1i+1ji
于是转移写成

fi+1,i+1+jfi,jif ji

时间复杂度 O(n2),足以通过 n5000

Subtask 710

上述 dp 已经不好优化,考虑重新设计状态。
不妨设计容斥,对于一组不合法方案,找到最小的 i,满足 [1,i] 中所有的数都可以凑出,i+1 无法凑出。
那么就可以转化为,对于一个位置 i,满足 i 是最小的满足值域在 [1,i]恰好凑出元素和为 i的值。
于是状态变为设 fi 表示满足选择的集合值域在 [1,i],能拼出 [1,i],且元素和恰好为 i 的方案数。
更具体地,我们枚举第一个不能被表示出来的数,在计算 fi 时,如果 fj 要向 fi 转移,需要满足

  • [1,j] 均可以被表示
  • j+1 不能被表示
  • [j+2,i] 凑出来的数和为 ij

接下来就需要计算 [1,j] 的互异拆分数与 [j+2,i] 的互异拆分数。
有一个结论:ai=n,lenan
可以将互异拆分数看作一个阶梯。

如图所示,此时 a1<a2<<ak,且 1kai=n
朴素的题目一般给定了拆分部分 k,时间复杂度为 O(nk)
在本题会退化成 O(nnn=n2)
但此时这个做法还是与暴力同分。
考虑如何优化构造,我们将整个图竖过来。

那么现在构造转换成了对于 i[1,n],求有多少个数大于等于 i
在上张图中,不难发现有大于等于 i 的数有 5,4,3,3,2,1,可以发现 i 不一定小于 n,但一定是两倍级别内的。
考虑这样一个数列是递减的,可以从 n1 倒序枚举 i,每次加入一个面积为 i 的矩形,表示有 i 个元素大于等于某个值。
对于一个 i,考虑 j 在贡献的时候其他元素大小应该大于等于 j+2。设 x[j+2,i] 的集合中数的个数时,j 可以贡献到 j+(j+2)×x,因为计算 [j+2,i] 时只要知道 fij,且每个值比原来计算出来的少了 j+2
如果我们要让 j 贡献到 i,只需要满足 ij+(j+2)。因此可以借助分治思想,每次处理 i2
复杂度为 T(n)=O(nn)+T(n2)=O(nn)

Code

void work(int n) {
	if (n <= 1) return;
	work(n / 2);
	int lim = sqrt(n << 1);
	for (int i = lim; i >= 1; --i) {
		for (int j = n; j >= i; --j)
			g[j] = g[j - i];
		for (int j = 0, k = 2 * i; k <= n; ++j, k += i + 1)
			add(g[k], f[j]);
		for (int j = i; j <= n; ++j) add(g[j], g[j - i]);
	}
	for (int i = n / 2 + 1; i <= n; ++i) add(f[i], -g[i] + p);
	for (int i = 0; i <= n; ++i) g[i] = 0;
}

int main() {
	cin >> n >> p;
	pw[0] = 1;
	for (int i = 1; i <= n; ++i) pw[i] = (pw[i - 1] << 1) % p;
	int lim = sqrt(n << 1);
	for (int i = lim; i >= 1; --i) {
		for (int j = n; j >= i; --j) f[j] = f[j - i];
		add(f[i], 1);
		for (int j = i; j <= n; ++j) add(f[j], f[j - i]);
	}
	f[0] = 1;
	work(n);
	int ans = 0;
	for (int i = 0; i < n; ++i) add(ans, 1ll * f[i] * pw[n - i - 1] % p);
	cout << (pw[n] - ans + p) % p << endl;
	return 0;
}
posted @   MisterRabbit  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
点击右上角即可分享
微信分享提示