NOI2015 寿司晚宴

NOI2015 寿司晚宴

为什么没有人用容斥呢? 本做法复杂度 O(38n)

题意

Link

给正整数 [2,n],选两个不交子集,使得两个子集中任意两个数互质。求方案数。

题目转化

分析性质,发现对于每个质数 p,两个数集中只有一个数集存在整除 p 的元素。所以状态就是两个集合分别包含的质因数集合即可,必须要求两个集合 & 后是 0

n 以内有 m 个质数,则复杂度是 O(22mn)

根号分治

发现 500 以内的数,大于 19 的质因数最多有一个。

小于等于 19 的质数只有 2,3,5,7,11,13,17,19,共 8 个。如果我们把包含大于 19 质因数的数字先剔除不计,仅分配剩下这些质因数,那么每个人的集合有 28 种情况,两个人的状态数是 48 种,因为两个人的集合不交,每个元素的状态只有三种,在第一个集合中,在第二个集合中,不存在,所以一共是 38 种有效状态。

假设我们现在确定了决策完未剔除的数字后,两个人的质因数集合。那么对于剔除的数字,我们可以枚举 19 后面的质数,将所有以这个质数为最大质因数的数同时考虑,枚举三种情况,分别是把这些数按 19 以内的质因数约束分配到第一个人的集合里,分配到第二个人的集合里,不分配。因为是按照确定的集合分配的,所以 19 以内质因数是确定的,19 以后的质因数也不会在待处理的数中出现,所以是正确的。

枚举 S 直接求 fS

我们把状态压成三进制,称为集合 S。每个 S 唯一对应一个有序二进制集合二元组 (A,B)。其中 A&B=0 表示两个人 19 以内的因数情况。S 的第 i 位是 0,则 AB 的第 i 位都为 0,如果 S 的第 i 位是 12,则分别对应 A 的第 i 位为 1B 的第 i 位为 1

对于每个 S,我们把 [2,n] 每个数按照除以 19 以内所有质因数的结果分类,可以算出 fS 表示选出的两个不交子集各自的质因数,分别是由 S 确定的 AB 的子集的方案数。

定义三进制集合的 PopCnt 为这个集合不为 0 的元素个数。我们发现对于一个方案 x,这个方案两个人的 19 以内的质因数集合分别是 AxBx,这两个集合可以表示为三进制集合 Sx。那么它不仅会被 fSx 统计,还会被 Sx 的真超集的 f 值所统计。

那么对于一个满足自己对应的 Sx 的 PopCnt 为 i 的方案,被 PopCnt 为 jS (ji) 所统计的次数,也就是 Sx 的 PopCnt 为 j 超集数量,即为:

gj,i=(8i8j)2ji

式子很容易理解,组合数就是枚举哪些在 Sx 中为 0 的位置在 S 中也为 0,后面的 2ji 则是讨论在 Sx 中为 0 但是在 S 中不为 0 的位置,到底取 1 还是取 2,互相独立,满足乘法原理条件。

容斥

由上面的式子我们发现如果简单给 fS 求和,一个方案会被统计多次。所以考虑用容斥把答案凑出来。

因为方案 x 的统计次数只和 Sx 的 PopCnt 有关,所以 PopCnt 相同的 Sfi 应当是同时考虑的,所以我们定义

Sumi=PopCnt(S)=ifS

也就是说我们希望能有一个数列 a,使得

Ans=i=08aiSumi

结合前面 g 的表达式,那么对 a 的要求就是: 可以使得对于所有 i,有

j=i8aigj,i=1

g 的形式一眼会让人联想到二项式反演,但是在无聊的合格考过程中,我惊奇地发现:

j=in(ninj)2ji(1)nj=1

也就是说

j=i8(1)jgj,i=1ai=(1)i

至于原因,我百思不得其解,但是只需要对每个 S 求方案数,然后根据 S 的元素数乘上相应的 a 对答案进行统计即可。

代码实现

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;
}
posted @   Wild_Donkey  阅读(53)  评论(1编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示