【NOI2015】寿司晚会 题解(状压DP)
新博客:debug18.com 使用hexo搭建,欢迎来踩~
【问题描述】 为了庆祝 NOI 的成功开幕,主办方为大家准备了一场寿司晚宴。小 G 和小 W 作为参加 NOI 的选手,也被邀请参加了寿司晚宴。 在晚宴上,主办方为大家提供了 n−1 种不同的寿司,编号 1,2,3,⋯, n−1, 其中第 i 种寿司的美味度为 i+1 (即寿司的美味度为从 2 到 n )。 现在小 G 和小 W 希望每人选一些寿司种类来品尝,他们规定一种品尝方案 为不和谐的当且仅当:小 G 品尝的寿司种类中存在一种美味度为 x 的寿司,小 W 品尝的寿司中存在一种美味度为 y 的寿司,而 x 与 y 不互质。 现在小 G 和小 W 希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数 p 取模)。注意一个人可以不吃任何寿司。
【输入格式】 输入文件的第 1 行包含 2 个正整数 , (2 ≤ n ≤ 500, 0 < p ≤ 10,000,000,000),中间用单个空格隔开,表示共有 种寿司,最终和谐的方案数要对 取模。
【输出格式】 输出一行包含 1 个整数,表示所求的方案模 p 的结果。
注意:此题我的做法较为复杂,但与其他做法的思路都是差不多的,不过代码写得丑没办法……
这是我第一次参加NOI“同步赛的同步赛”23333。用老师的话说,“感受一下气氛”……
day1第一题水过,看了看第二题:我去好像是树链剖分!我去树链剖分昨天才学的啊!……然后我就默默地打了个暴力QAQ
然后便是第三题。刚看到这题,似乎没有什么思路呢……试着想了想状压DP,但500内有80多个质数,很不可做的样子(反正就是想不出来)。然后就默默地打了个表QAQ
果然我还是太弱了……不多说了,上题解。(这是我看大神的代码yy出来的,若有错误请一定指出来,不胜感激)
首先明确,选了一个数实际上就是选了它的质因子,这样便只用考虑质因子。
显然对于任意数 a , 若对其质因数分解,至多有一个质因数大于 sqrt(n) 。因此,可以分开来计算两种情况,最后再合并求出答案:
-
对于只含有小于等于 sqrt(n) 的质因数 p 的情况:
-
设 f[S] 为某一人选取的质因数为集合 S 时的方案数(S 中的元素一定要全部用到!)
-
很容易得出 f[S] = 2^|a| – sigma(S0|S0⊂S) (a 为质因数全部被 S 包含的数的集合)
-
-
对于含有大于 sqrt(n) 的质因数 p0 的情况:
-
设 g[i][S1][S2] 为在前 i 个大于 sqrt(n) 的质数及其倍数中,第一个人选取的小于 sqrt(n) 的质因数为 S1 ,另一人选取的为 S2 时的方案数
-
对于每个 p0[i] 只有两个选择:被第一个人选走或是被第二个人选走(不选的情况被包括了两次,故最后需减去)
-
若 p0[i] 被第一个人选走:
-
设 t[k][S1][S2] 为算到当前质数的前 k 个倍数,第一个人选取的小于 sqrt(n) 的质因数为 S1 ,另一人选取的为 S2 时的方案数
-
设 S0 为对当前质数的 k + 1 倍进行质因数分解得到的小于 sqrt(n) 的质数的集合
-
则 t[k][S1][S2] 可以转移到 t[k+1][S1][S2] 和 t[k+1][S1∪S0][S2] ((S1 ∪ S0) ∩ S2 = ∅)
-
边界: t[0][S1][S2] = g[i-1][S1][S2]
-
-
p0[i] 被第二个人选走的情况同理
-
-
最后,枚举 Sa 和 Sb (Sa ∩ Sb = ∅),再枚举 S1 和 S2 使得 (Sa ∪ S1) ∩ (Sb ∪ S2) = ∅, ans 累加 f[Sa] * f[Sb] * g[m][S1][S2] 即可(m 为 p0 的大小)
注意到 g 、t 很大,而它们又只与上一个相关,故可以省掉第一维。
好吧说实话我是看别人代码才会做这题的……
一定要注意不能用memset、memcpy!!!本以为 3^n 和 4^n 差别不大,于是就TLE了……
实现有很多细节,见代码:(蒟蒻代码写得很挫……见谅)
#include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <cctype> #include <cassert> #include <ctime> #include <algorithm> #include <utility> #include <functional> #include <iostream> using namespace std; typedef long long LL; const int MAXN = 505, MAXM1 = 10, MAXM2 = 100, MAXS = 1 << MAXM1; int N; LL P; /// M1 : the number of primes <= sqrt(N) /// M2 : the number of primes > sqrt(N) /// PS[i] : the set of primes of number i
/// A[i][j]: the i-th primes Pi * j <= N (Pi > sqrt(N)) int M1, M2, PS[MAXN], A[MAXM2][30]; int SBound, SAll; LL f[MAXS]; LL g[2][MAXS][MAXS]; int Gidx; inline void add(LL &a, LL b) { a += b; if (a >= P) /// ERR#2: ">" instead of ">=" a -= P; if (a < 0) a += P; } inline LL mul(LL a, LL b) { return a * b % P; } inline void add(LL a[][MAXS], const LL b[][MAXS], int d) { for (int S = 0; S < SBound; S++) { int SS = SAll ^ S, S0; for (S0 = SS; S0; S0 = (S0 - 1) & SS) add(a[S][S0], d * b[S][S0]); add(a[S][S0], d * b[S][S0]); } } inline void cpy(LL a[][MAXS], const LL b[][MAXS]) { for (int S = 0; S < SBound; S++) { int SS = SAll ^ S, S0; for (S0 = SS; S0; S0 = (S0 - 1) & SS) a[S][S0] = b[S][S0]; a[S][S0] = b[S][S0]; } } void init() { static bool flag[MAXN]; M1 = M2 = 0; for (int i = 2; i <= N; i++) { if (flag[i]) continue; PS[i] |= 1 << M1; if (i * i <= N) { for (int j = 2 * i; j <= N; j += i) { flag[j] = true; PS[j] |= 1 << M1; } M1++; } else { M2++; A[M2][A[M2][0]=1] = i; for (int j = 2 * i; j <= N; j += i) { flag[j] = true; PS[j] |= 1 << M1; A[M2][++A[M2][0]] = j; } } } SBound = 1 << M1; SAll = SBound - 1; } void calcF() { for (int S = 0; S < SBound; S++) { f[S] = 1; for (int i = 2; i <= N; i++) if ((S | PS[i]) == S) add(f[S], f[S]); int S0; for (S0 = (S - 1) & S; S0; S0 = (S0 - 1) & S) /// ERR#3: "S0 = S" instead of "S0 = (S - 1) & S" add(f[S], -f[S0]); if (S) add(f[S], -f[S0]); } } LL t[2][MAXS][MAXS]; LL empty[MAXS][MAXS]; void selectA(int a[]) { for (int i = 1; i <= a[0]; i++) { int cur = i & 1, last = cur ^ 1; int mask = PS[a[i]] ^ SBound; cpy(t[cur], empty); for (int S1 = 0; S1 < SBound; S1++) { int SS = SAll ^ S1, S2; for (S2 = SS; S2; S2 = (S2 - 1) & SS) { if (((S1 | mask) & S2) == 0) add(t[cur][S1|mask][S2], t[last][S1][S2]); add(t[cur][S1][S2], t[last][S1][S2]); } add(t[cur][S1|mask][S2], t[last][S1][S2]); add(t[cur][S1][S2], t[last][S1][S2]); } } } void selectB(int a[]) { for (int i = 1; i <= a[0]; i++) { int cur = i & 1, last = cur ^ 1; int mask = PS[a[i]] ^ SBound; cpy(t[cur], empty); for (int S1 = 0; S1 < SBound; S1++) { int SS = SAll ^ S1, S2; for (S2 = SS; S2; S2 = (S2 - 1) & SS) { if ((S1 & (S2 | mask)) == 0) add(t[cur][S1][S2|mask], t[last][S1][S2]); add(t[cur][S1][S2], t[last][S1][S2]); } if ((S1 & mask) == 0) add(t[cur][S1][S2|mask], t[last][S1][S2]); add(t[cur][S1][S2], t[last][S1][S2]); } } } void calcG() { g[0][0][0] = 1; for (int i = 1; i <= M2; i++) { int cur = i & 1, last = cur ^ 1, curt = A[i][0] & 1; cpy(g[cur], empty); /// ERR#1: forgot reset g[cur] cpy(t[0], g[last]); selectA(A[i]); add(g[cur], t[curt], 1); cpy(t[0], g[last]); selectB(A[i]); add(g[cur], t[curt], 1); add(g[cur], g[last], -1); } Gidx = M2 & 1; } LL solve(int Sa, int Sb) { if (Sa > Sb) return 0; LL res = 0, base = mul(f[Sa], f[Sb]); for (int S1 = 0; S1 < SBound; S1++) { int SS = SAll ^ S1, S2; for (S2 = SS; S2; S2 = (S2 - 1) & SS) if (((Sa | S1) & (Sb | S2)) == 0) add(res, mul(base, g[Gidx][S1][S2])); if (((Sa | S1) & (Sb | S2)) == 0) add(res, mul(base, g[Gidx][S1][S2])); } if (Sa != Sb) add(res, res); /// e.g. res *= 2; return res; } int main() { freopen("dinner.in", "r", stdin); freopen("dinner.out", "w", stdout); cin >> N >> P; init(); calcF(); calcG(); LL ans = 0; for (int S1 = 0; S1 < SBound; S1++) { int SS = SAll ^ S1, S2; for (S2 = SS; S2; S2 = (S2 - 1) & SS) add(ans, solve(S1, S2)); add(ans, solve(S1, S2)); } cout << ans << endl; return 0; }