Loading

【题解】P2150 [NOI2015] 寿司晚宴

题意

P2150 [NOI2015] 寿司晚宴

对于 \(2\)\(n\)\(n - 1\) 个自然数,考虑从中选取一些数并将其分成两部分,使得从两部分中各取任意一个数 \(x, y\) 都满足 \(x, y\) 互质。求选数并分数的方案总数,结果对给定常数 \(p\) 取模。

对于 \(30\%\) 的数据,\(2 \leq n \leq 30\)

对于所有数据,\(2 \leq n \leq 500, 0 < p \leq 10^9\)

思路

状压 dp

先考虑 \(30\%\) 的部分分。容易发现判断是否互质只与两部分中每个数含有的质因子有关,考虑到 \(30\) 以内的质数只有 \(10\) 个,故可以设 \(dp[S1][S2]\) 表示部分一中所有数的质因子并集为 \(S1\)\(S2\) 同理的方案总数。不妨预处理出 \([2, n]\) 中每个数的质因子的并集,记为 \(k\),容易得到状态转移方程(刷表法):

\(S1 \cap S2 = \empty\)\(k \cap S2 = \empty\) 时,\(dp[S1 \mid k][S2] += dp[S1][S2]\)

\(S1 \cap S2 = \empty\)\(k \cap S1 = \empty\) 时,\(dp[S1][S2 \mid k] += dp[S1][S2]\)

时间复杂度是 \(O(2^{10} n)\)

容易发现时间复杂度的瓶颈在于状压的质数个数,\(500\) 以内的质数共有 \(95\) 个,因此需要优化。

易得:对于自然数 \(n\),其大于 \(\sqrt{n}\) 的质因子至多只有 \(1\) 个。

因此可得对于 \(500\) 以内的自然数,其大于 \(\sqrt{500}\)(约为 \(22\))的质因子只有 \(1\) 个。

所以可以考虑把大于 \(22\) 的质因子单独提出来判断。对于 \([2, n]\) 内每一个可能被选中且大于 \(22\) 的质因子,讨论其对答案的贡献。因为大于 \(22\) 的质因子额外讨论,状态只需要记录 \(22\) 以内的质因子是否被选中,即至多有 \(2^8\) 种状态(\(22\) 以内的质数共 \(8\) 个)

考虑记录下 \([2, n]\) 中每一个数的质因子子集 \(k\) 和其大于 \(22\) 的唯一质因子 \(p\)。直接上手不好维护,考虑将所有数按照 \(p\) 分类。对于 \(p\) 相同的数,将其划为一段,分类讨论:

  1. \(p\) 在部分一内

  2. \(p\) 在部分二内

  3. \(p\) 既不在部分一内,也不在部分二内

由于状态只压到 \(22\) 以内的质数,\(p\) 被分在不同部分的情况需要分类讨论。设 \(f1[S1][S2]\) 表示部分一中所有数 \(22\) 以内的质因子并集为 \(S1\),部分二的为 \(S2\) 且所有被 \(p\) 整除的数都不在部分二内的情况。\(f2[S1][S2]\) 同理。对于 \(p \mid x\),容易得到状态转移方程:

\(S1 \cap S2 = \empty\)\(k \cap S2 = \empty\) 时,\(f1[S1 \mid k][S2] += f1[S1][S2]\)

\(S1 \cap S2 = \empty\)\(k \cap S1 = \empty\) 时,\(f2[S1][S2 \mid k] += f1[S1][S2]\)

每一段开始时,将 \(dp\) 赋值到 \(f1, f2\) 作为初值。当每一段结束以后,考虑重新用 \(f1, f2\) 合并成 \(dp\)。容易发现 \(f1, f2\) 中重复的部分为两部分均没有 \(p\) 的情况,其并集为 部分一包含 \(p\) 并 部分二包含 \(p\) 并 均不包含 \(p\),所以得到:

\(dp[S1][S2] = f1[S1][S2] + f2[S1][S2] - dp[S1][S2]\)

最终的答案为所有合法状态的答案之和。

时间复杂度 \(O(2^{16} n)\)

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;

const int maxn = 505;
const int maxs = (1 << 8);

struct item
{
    int fac, sp;
    bool operator < (const item& rhs) const { return (sp < rhs.sp); }
} num[maxn];

int n, p;
int prime[8] = {2, 3, 5, 7, 11, 13, 17, 19};
int dp[maxs][maxs], f1[maxs][maxs], f2[maxs][maxs];

void init()
{
    for (int i = 2; i <= n; i++)
    {
        int cur = i;
        num[i - 1].sp = -1;
        for (int j = 0; j < 8; j++)
        {
            if (!(cur % prime[j]))
            {
                num[i - 1].fac |= (1 << j);
                while (!(cur % prime[j])) cur /= prime[j];
            }
        }
        if (cur > 1) num[i - 1].sp = cur;
    }
    sort(num + 1, num + n);
}

int main()
{
    int ans = 0;
    scanf("%d%d", &n, &p);
    init();
    dp[0][0] = 1;
    for (int i = 1; i <= n - 1; i++)
    {
        if ((num[i].sp != num[i - 1].sp) || (num[i].sp == -1))
        {
            memcpy(f1, dp, sizeof(f1));
            memcpy(f2, dp, sizeof(f2));
        }
        for (int j = maxs - 1; j >= 0; j--)
        {
            for (int k = maxs - 1; k >= 0; k--)
            {
                if (j & k) continue;
                if (!(num[i].fac & k)) f1[j | num[i].fac][k] = (ll)(f1[j | num[i].fac][k] + f1[j][k]) % p;
                if (!(num[i].fac & j)) f2[j][k | num[i].fac] = (ll)(f2[j][k | num[i].fac] + f2[j][k]) % p;
            }
        }
        if ((num[i].sp != num[i + 1].sp) || (num[i].sp == -1))
        {
            for (int j = 0; j < maxs; j++)
                for (int k = 0; k < maxs; k++)
                    if (!(j & k)) dp[j][k] = (ll)((f1[j][k] + f2[j][k]) % p - dp[j][k] % p + p) % p;
        }
    }
    for (int i = 0; i < maxs; i++)
        for (int j = 0; j < maxs; j++)
            if (!(i & j)) ans = (ll)(ans + dp[i][j]) % p;
    printf("%d\n", ans);
    return 0;
}
posted @ 2022-07-01 20:22  kymru  阅读(72)  评论(0编辑  收藏  举报