嘘~ 正在从服务器偷取页面 . . .

洛谷P2150 [NOI2015] 寿司晚宴 题解


洛谷P2150 [NOI2015] 寿司晚宴 题解

题目链接:P2150 [NOI2015] 寿司晚宴

题意

为了庆祝 NOI 的成功开幕,主办方为大家准备了一场寿司晚宴。小 G 和小 W 作为参加 NOI 的选手,也被邀请参加了寿司晚宴。

在晚宴上,主办方为大家提供了 \(n−1\) 种不同的寿司,编号 \(1,2,3,\ldots,n-1\),其中第 \(i\) 种寿司的美味度为 \(i+1\)。(即寿司的美味度为从 \(2\)\(n\)

现在小 G 和小 W 希望每人选一些寿司种类来品尝,他们规定一种品尝方案为不和谐的当且仅当:小 G 品尝的寿司种类中存在一种美味度为 \(x\) 的寿司,小 W 品尝的寿司中存在一种美味度为 \(y\) 的寿司,而 \(x\)\(y\) 不互质。

现在小 G 和小 W 希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数 \(p\) 取模)。

注意一个人可以不吃任何寿司。

省流:

令两个集合为 \(\{2,3,\cdots,n\}\) 的子集,要求两个集合的元素必须两两互质,求方案数。

输入格式

输入文件的第 \(1\) 行包含 \(2\) 个正整数 \(n, p\) 中间用单个空格隔开,表示共有 \(n\) 种寿司,最终和谐的方案数要对 \(p\) 取模。

输出格式

输出一行包含 \(1\) 个整数,表示所求的方案模 \(p\) 的结果。

数据范围

\(2 \le n \le 500,~1 \le p \le 10^9\)​​ 。

首先可以发现,一个方案和谐当且仅当两个集合包含的质数没有交集。

注意到 \(500\) 以内的数最多只有一个比 \(22\) 大的质因子

那么只需要记录这个质因子就好状压了,因为剩下最多有 \(8\) 个质因子

\(f(S_1,S_2)\) 为两个集合分别吃了状态 \(S_1,S_2\) 对应的寿司时的方案数

枚举 \(i\) 并考察它落在了哪个集合里,这里需要临时记录个 \(f_1,f_2\) 来转移,详见代码。

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

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
const int p[]={0,2,3,5,7,11,13,17,19}; int mod;
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
void add(int &x,int y) { (x += y) >= mod ? x -= mod : 0; }
#define N ((int)(555))
#define M ((int)(333))

int dp[M][M], f1[M][M], f2[M][M];
struct node
{
    int v, mx, S;
    node() = default;
    node(int val)
    {
        int tmp = v = val; mx = -1, S = 0;
        for(int i = 1; i <= 8; i++)
            if(tmp % p[i] == 0)
            {
                S |= (1 << (i - 1));
                while(tmp % p[i] == 0) tmp /= p[i];
            }
        if(tmp > 1) mx = tmp;
    }
}a[N];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int n; cin >> n >> mod; dp[0][0] = 1;
    for(int i = 2; i <= n; i++) a[i - 1] = node(i);
    sort(a + 1, a + 1 + n, [](node a, node b) { return a.mx < b.mx; });
    for(int i = 1; i < n; i++)
    {
        if(i == 1 || a[i].mx != a[i - 1].mx || a[i].mx == -1) {
            memcpy(f1, dp, sizeof(f1)); memcpy(f2, dp, sizeof(f2));
        }
        for(int j = 255; ~j; j--)
            for(int k = 255; ~k; k--) if(!(j & k)) {
                if(!(a[i].S & j)) add(f2[j][k | a[i].S], f2[j][k]);
                if(!(a[i].S & k)) add(f1[j | a[i].S][k], f1[j][k]);
            }
        if(i == n - 1 || a[i].mx != a[i + 1].mx || a[i].mx == -1)
        {
            for(int j = 0; j <= 255; j++)
                for(int k = 0; k <= 255; k++) if(!(j & k)) {
                    dp[j][k] = (f1[j][k] + f2[j][k] + mod - dp[j][k]) % mod;
                }
        }
    }
    int res = 0;
    for(int j = 0; j <= 255; j++)
        for(int k = 0; k <= 255; k++)
            if(!(j & k) && dp[j][k]) add(res, dp[j][k]);
    cout << res << '\n';
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/article/y14n1niu


题外话

感觉这题相较于其他紫题好像略显简单,但也没简单到哪去。


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录