P2150 寿司晚宴 Solution
Simplification for the Question
- 有 总共 个数。
- 从中挑选一些不重复的数字(可以不全选),组成集合 和 。
- 求使得 的方案总数。
Part 1 - 30pts Algorithm
首先看到最简单的 30pts 算法,最暴力的部分总能给我们启示。
对于这 30pts,观察到 。
那么这意味着什么呢?对,就是暴力。
首先看到 ,求最大公约数,由唯一分解定理可知每一个数都可以由若干质数或质数的幂的连乘积构成,那么显然问题转化为:求使得 ,设 包含的质数集合为 ,设 包含的质数集合为 ,有 ,像这样的方案总数。
由于一个数的整数因数一定 它本身,所以说最大的质数一定 ,即 。
其中 表示当前情况的质数集合。
很显然,经过枚举,我们发现 以内有 个质数(
所以直接暴力状压 DP,将质数的包含情况作为状态,包含为 ,否则为 。
那么这个就是 30pts 的暴力算法了,时间复杂度 ,其中 为质数个数。
Part 2 - ? pts Algorithm
我也不知道中间那两档分是用来干什么的。
所以直接看正解算了吧。/kel
Part 3 - 100pts Algorithm
首先我们判一个数是否是质数,最暴力的是这样判的。
- 试除法
大概就长这样
function (int n)
if n:0 / n:1 -> back(0)
for i in range(2,sqrt(n))
if !(n mod i) -> back(0)
back(1)
那么看到这个 ,这个就是解题的重点。
首先 。
接着,分两种情况讨论质数 。
那么设 ,则显然 。
又因为因数不大于这个数本身,所以 的数最多只有两个,即 的数最多只有一个。
这个不用讲了吧。
那么又看到我们之前的暴力。
我们搞暴力,就是处理 以内的质数。
但是现在我们发现 的数最多只有一个,那么只要处理 的数,并且新开一个记录这个 的数即可。
然后我们又看到 。
而
所以说这个暴力就是个铺垫嘛,处理 反而只有八个质数。
所以我们就想到了正解,也就是,同样枚举八个质数的情况,然后再根据大质数来判断是否可选即可。
但是我们怎么能保证大质数呢?
很显然,直接大质数从小到大结构体排个序即可。
然后对于两个人取的 DP 数,直接暴力处理然后合并即可。
注意合并的时候要容斥,因为统计会同时处理两个人都不选的情况,所以在合并的时候要减掉一个原来的数值。
恭喜你,你又 A 掉了一道黑题。
时间复杂度优化为恒定
其中有一个地方要注意一下,就是在判断一开始预处理的质数情况时,贡献一定要加到对面去而不是自己的地方,因为如果加到自己的地方,无法保证对面和自己没有重复的元素,但是因为我们已经判断过这个质数和自己的关系,所以即使这个质数情况与对面有重复,加到一起仍然与自己合法,因为一定不会有重复的地方。
#include <bits/stdc++.h>
#define ri register int
using namespace std;
const int N = 5e2 + 10;
const int M = 1<<8;
const int pr[8] = {2,3,5,7,11,13,17,19};
int n, Mod, dp[M][M];
int Mer1[M][M], Mer2[M][M];
// Merge after dealing with, to DP
void read(int &ret) {
ret = 0; char ch = getchar();
while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) {
ret = (ret<<1) + (ret<<3) + (ch^48);
ch = getchar();
}
return ;
}
struct Number {
int value;
int MaxiPrime;
int OtherPrime;
void dealwith() {
// deal with possible primes
int cur = value;
for (ri i=0; i<8; ++i) {
// choose the primes
if (cur % pr[i]) continue;
// can't be devided with no left
OtherPrime |= (1<<i);
while (!(cur % pr[i])) cur /= pr[i];
}
if (cur > 1) MaxiPrime = cur;
return ;
}
bool operator <(const Number &X) const {
return MaxiPrime < X.MaxiPrime;
}
} a[N];
void init() {
read(n), read(Mod);
for (ri i=2; i<=n; ++i)
a[i-1].value = i, a[i-1].dealwith();
sort(a+1, a+n), dp[0][0] = 1;
return ;
}
void Merge(ri &x, ri y) {
x = (x + y) % Mod;
return ;
}
int main() {
init();
// predict:
// Rolling Array, Back-to-Head Scan!
for (ri i=1; i<n; ++i) {
if (i == 1 || a[i].MaxiPrime ^ a[i-1].MaxiPrime || !a[i].MaxiPrime)
memcpy(Mer1, dp, sizeof Mer1),
memcpy(Mer2, dp, sizeof Mer2);
// just copy from dp to Merge
for (ri j=M-1; j>=0; --j)
for (ri k=M-1; k>=0; --k) {
// enumarate each possible situation
// which is of the two people
if (j&k) continue;
// has the same elements
if (!(j&a[i].OtherPrime))
Merge(Mer1[j][k|a[i].OtherPrime], Mer1[j][k]);
if (!(k&a[i].OtherPrime))
Merge(Mer2[j|a[i].OtherPrime][k], Mer2[j][k]);
// 需要注意的就是这里,不要写反了
}
if (i == n-1 || a[i].MaxiPrime ^ a[i+1].MaxiPrime || !a[i].MaxiPrime)
for (ri j=0; j<M; ++j)
for (ri k=0; k<M; ++k) {
if (j&k) continue;
// has the same elements
ri to = Mer1[j][k] + Mer2[j][k] - dp[j][k];
to %= Mod, dp[j][k] = to;
dp[j][k] = (dp[j][k] + Mod) % Mod;
}
}
ri ans = 0;
for (ri i=0; i<M; ++i)
for (ri j=0; j<M; ++j)
if (!(i&j)) Merge(ans, dp[i][j]);
cout << ans << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现