bzoj 4197 [Noi2015]寿司晚宴 状压dp

本文介绍了一种高效解决质因数分解与组合计数问题的方法,通过将质因子的状态压缩为二进制数并使用动态规划进行转移,实现了在一定范围内快速计算质因子的组合方案数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题面

题目传送门

解法

思路是真的神仙

  • 考虑当 n n n比较小的时候怎么做,因为质因子的个数不多,所以将是否取质因子的状态压成一个二进制数然后转移即可
  • 然后发现 n ≤ 500 n≤500 n500,质因子个数还是比较多的
  • 那么我们对于每一个数都分开考虑,可以发现最多只会有一个质因子 > n >\sqrt n >n
  • 计算一下 n \sqrt n n 22 22 22左右,在这个范围内的质数很少,只有8个
  • 那么我们可以将这些质因子的取值状态压成一个二进制数,设 f [ i ] [ j ] f[i][j] f[i][j]表示第一个人选择了质因子集合 i i i,第二个人选择了质因子集合 j j j的方案数
  • 然后做法可以说就比较显然了:对于每一个数先求出大于 n \sqrt n n 的质因子,然后按照这个排序,将大于 n \sqrt n n 的质因子相同的放入一组。对于同一组里的数,我们可以进行这样一个dp:设 f 1 [ i ] [ j ] f1[i][j] f1[i][j]表示这个因子给第一个人, f 2 [ i ] [ j ] f2[i][j] f2[i][j]表示这个因子给第二个人的方案数。转移十分显然,这里就不必再赘述了
  • 考虑将同一组里的全部求解完毕之后怎么把答案合并到 f [ i ] [ j ] f[i][j] f[i][j]上,因为在计算的时候会重复计算两个人都不选择这个质因子的情况,所以 f [ i ] [ j ] = f 1 [ i ] [ j ] + f 2 [ i ] [ j ] − f [ i ] [ j ] f[i][j]=f1[i][j]+f2[i][j]-f[i][j] f[i][j]=f1[i][j]+f2[i][j]f[i][j]
  • 因为这里开的是类似于滚动数组的东西,所以在求解 f 1 , f 2 f1,f2 f1,f2的时候下标应该倒着枚举,类似于0/1背包
  • 时间复杂度: O ( n 2 16 ) O(n2^{16}) O(n216)

代码

#include <bits/stdc++.h>
#define N 550
using namespace std;
template <typename node> void chkmax(node &x, node y) {x = max(x, y);}
template <typename node> void chkmin(node &x, node y) {x = min(x, y);}
template <typename node> void read(node &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
int prime[9] = {0, 2, 3, 5, 7, 11, 13, 17, 19};
int f1[N][N], f2[N][N], ans[N][N];
struct Info {
	int s, key;
	bool operator < (const Info &a) const {return key < a.key;}
	void divide(int x) {
		for (int i = 1; i <= 8; i++) {
			if (x % prime[i] != 0) continue;
			s |= 1 << i - 1;
			while (x % prime[i] == 0) x /= prime[i];
		}
		key = x;
	}
} a[N];
int main() {
	int n, Mod; read(n), read(Mod);
	for (int i = 2; i <= n; i++) a[i].divide(i);
	sort(a + 2, a + n + 1); ans[0][0] = 1;
	for (int i = 2; i <= n; i++) {
		if (a[i].key == 1 || a[i].key != a[i - 1].key || i == 2)
			memcpy(f1, ans, sizeof(f1)), memcpy(f2, ans, sizeof(f2));
		for (int j = 255; ~j; j--)
			for (int k = 255; ~k; k--) {
				if ((j & k) > 0) continue;
				if ((k & a[i].s) == 0) f1[j | a[i].s][k] = (f1[j | a[i].s][k] + f1[j][k]) % Mod;
				if ((j & a[i].s) == 0) f2[j][k | a[i].s] = (f2[j][k | a[i].s] + f2[j][k]) % Mod;
			}
		if (i == n || a[i].key == 1 || a[i].key != a[i + 1].key)
			for (int j = 0; j <= 255; j++)
				for (int k = 0; k <= 255; k++) {
					if ((j & k) > 0) continue;
					ans[j][k] = ((f1[j][k] + f2[j][k]) % Mod - ans[j][k] + Mod) % Mod;
				}
	}
	int ret = 0;
	for (int i = 0; i <= 255; i++)
		for (int j = 0; j <= 255; j++)
			if ((i & j) == 0) ret = (ret + ans[i][j]) % Mod;
	cout << ret << "\n";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值