NOI2015 寿司晚宴
NOI2015 寿司晚宴
为什么没有人用容斥呢? 本做法复杂度 。
题意
给正整数 ,选两个不交子集,使得两个子集中任意两个数互质。求方案数。
题目转化
分析性质,发现对于每个质数 ,两个数集中只有一个数集存在整除 的元素。所以状态就是两个集合分别包含的质因数集合即可,必须要求两个集合 后是 。
设 以内有 个质数,则复杂度是 。
根号分治
发现 以内的数,大于 的质因数最多有一个。
小于等于 的质数只有 ,共 个。如果我们把包含大于 质因数的数字先剔除不计,仅分配剩下这些质因数,那么每个人的集合有 种情况,两个人的状态数是 种,因为两个人的集合不交,每个元素的状态只有三种,在第一个集合中,在第二个集合中,不存在,所以一共是 种有效状态。
假设我们现在确定了决策完未剔除的数字后,两个人的质因数集合。那么对于剔除的数字,我们可以枚举 后面的质数,将所有以这个质数为最大质因数的数同时考虑,枚举三种情况,分别是把这些数按 以内的质因数约束分配到第一个人的集合里,分配到第二个人的集合里,不分配。因为是按照确定的集合分配的,所以 以内质因数是确定的, 以后的质因数也不会在待处理的数中出现,所以是正确的。
枚举 直接求
我们把状态压成三进制,称为集合 。每个 唯一对应一个有序二进制集合二元组 。其中 表示两个人 以内的因数情况。 的第 位是 ,则 , 的第 位都为 ,如果 的第 位是 或 ,则分别对应 的第 位为 和 的第 位为 。
对于每个 ,我们把 每个数按照除以 以内所有质因数的结果分类,可以算出 表示选出的两个不交子集各自的质因数,分别是由 确定的 , 的子集的方案数。
定义三进制集合的 PopCnt 为这个集合不为 的元素个数。我们发现对于一个方案 ,这个方案两个人的 以内的质因数集合分别是 和 ,这两个集合可以表示为三进制集合 。那么它不仅会被 统计,还会被 的真超集的 值所统计。
那么对于一个满足自己对应的 的 PopCnt 为 的方案,被 PopCnt 为 的 () 所统计的次数,也就是 的 PopCnt 为 超集数量,即为:
式子很容易理解,组合数就是枚举哪些在 中为 的位置在 中也为 ,后面的 则是讨论在 中为 但是在 中不为 的位置,到底取 还是取 ,互相独立,满足乘法原理条件。
容斥
由上面的式子我们发现如果简单给 求和,一个方案会被统计多次。所以考虑用容斥把答案凑出来。
因为方案 的统计次数只和 的 PopCnt 有关,所以 PopCnt 相同的 的 应当是同时考虑的,所以我们定义
也就是说我们希望能有一个数列 ,使得
结合前面 的表达式,那么对 的要求就是: 可以使得对于所有 ,有
的形式一眼会让人联想到二项式反演,但是在无聊的合格考过程中,我惊奇地发现:
也就是说
至于原因,我百思不得其解,但是只需要对每个 求方案数,然后根据 的元素数乘上相应的 对答案进行统计即可。
代码实现
const unsigned M(6561);
const unsigned Tri[10] = { 1,3,9,27,81,243,729,2187,6561 };
const unsigned Prime[10] = { 2,3,5,7,11,13,17,19 };
vector <unsigned> Bel[505];
unsigned long long Tmp(0), Mod(998244353), Ans(0);
unsigned PopCnt[7005], Need[7005][2];
unsigned Stack[505], STop(0), Have[505];
unsigned m, n;
unsigned A, B, D, t;
unsigned Cnt(0);
signed main() {
n = RD(), Mod = RD();
for (unsigned i(2); i <= n; ++i) {
unsigned Ti(i);
for (unsigned j(0); j < 8; ++j) {
if (!(Ti % Prime[j])) Have[i] |= (1 << j);
while (!(Ti % Prime[j])) Ti /= Prime[j];
}
if (Ti > 1) Stack[++STop] = Ti;
Bel[Ti].push_back(i);
}
for (unsigned i(0); i < M; ++i) {
for (unsigned j(0); j < 8; ++j) {
unsigned Jth((i / Tri[j]) % 3);
if (Jth) Need[i][(Jth & 1) ? 0 : 1] |= (1 << j);
}
}
sort(Stack + 1, Stack + STop + 1);
STop = unique(Stack + 1, Stack + STop + 1) - Stack - 1;
for (unsigned i(0); i < M; ++i) PopCnt[i] = PopCnt[i / 3] + (bool)(i % 3);
for (unsigned i(0); i < M; ++i) {
Tmp = 1;
for (auto j : Bel[1]) {
if ((Have[j] & Need[i][0]) == Have[j]) { Tmp <<= 1; if (Tmp >= Mod) Tmp -= Mod; }
if ((Have[j] & Need[i][1]) == Have[j]) { Tmp <<= 1; if (Tmp >= Mod) Tmp -= Mod; }
}
for (unsigned j(1); j <= STop; ++j) {
A = 1, B = 1;
for (auto k : Bel[Stack[j]]) {
if ((Have[k] & Need[i][0]) == Have[k]) { A <<= 1; if (A >= Mod) A -= Mod; }
if ((Have[k] & Need[i][1]) == Have[k]) { B <<= 1; if (B >= Mod) B -= Mod; }
}
Tmp = Tmp * (A + B - 1) % Mod;
}
Ans += (PopCnt[i] & 1) ? (Mod - Tmp) : Tmp;
if (Ans >= Mod) Ans -= Mod;
}
printf("%llu\n", Ans);
return Wild_Donkey;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)