【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;
}

posted on 2015-07-22 21:01  __debug  阅读(1723)  评论(1编辑  收藏  举报

导航