Loading

[联合省选 2022 D2T1] 卡牌

首先直接按题意模拟一下,发现“所有质数都要被选上”这个条件很烦,因为选上一个卡牌后有很多质数会受到影响,非常不好做。换言之,题中的限制很强。

考虑正难则反,钦定一些质数使得它们 恰好 没有被选上,其它质数都被选上。然后会发现 恰好 这个条件还是很强,不好搞,直接上容斥,把 恰好 弱化成 一部分 被选上,另一部分不用管。这样限制条件就松了很多。

回到题目,\(n\le 10^6\) 这个条件是唬人的,真正互不相同的数字只有 \(2000\) 个,拿桶记一下数 \(n\) 就可以扔了。

对于 \(s_i\le 30\) 的部分分,显然可以状压预处理然后容斥计算。这为我们提供了一些思路。

[NOI2015] 寿司晚宴 这道题给出了一个相当经典的处理手段:一个数至多有一个 \(\gt \sqrt n\) 的质因子,对于 \(\le \sqrt n\) 的质因子状压,对于 \(\gt \sqrt n\) 的质因子单独考虑。为了方便,称这类质因子为“大质因子”。

本题 \(V=2\times 10^3\),发现 \(\le \sqrt V\) 的质数只有 \(2\sim 43\) 这 14 个,进一步地,\(43\times 47\gt 2000\),所以 43 也不用参与状压,进一步压缩了状态。

然后统计,令 \(f(i,j)\) 表示前 13 个质数没有被选的状态为 \(i\),当前大质因子为 \(j\) 时的方案数。预处理是简单的。

然后我们考虑容斥,容易发现容斥系数为 \((-1)^{|S|}\)

计算答案并不难。对于一个状态 \(i\),枚举 \(j\in [43,2000],j\in \mathbb P\),如果 \(j\) 在询问集合中,那么有 \(2^{f(i,j)}-1\) 的贡献(筛去空集),反之有 \(2^{f(i,j)}\) 的贡献。

把询问集合存成 std::vector,每次就不用枚举 \(j\),直接扫一遍 std::vector 然后统计答案,和容斥系数乘起来加到答案里即可。实现可以参考代码。

时间复杂度 \(\mathcal O(\sum c_i\times 2^{13})\)

// 此时此刻的光辉,盼君勿忘。

#include <bits/stdc++.h>
#define pb emplace_back

const int N = 2e3;
const int M = 1 << 13;
const int mod = 998244353;
const int SIZE = 1e6 + 5;
int prime[N], cnt, buc[N + 5];
bool flag[N + 5];
std::vector<int> G[N + 5];

int pri[13] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 31}, pos[N + 5];

int m, n, sum[N + 5], res[M + 5], f[M + 5][305], pw[SIZE], popcnt[M + 5];

void add(int &x, int y) {
    x += y;

    if (x >= mod)
        x -= mod;

    return ;
}

void sub(int &x, int y) {
    x -= y;

    if (x < 0)
        x += mod;

    return ;
}

int power(int x, int y) {
    int ans = 1;

    for (; y; y >>= 1) {
        if (y & 1)
            ans = 1ll * ans * x % mod;

        x = 1ll * x * x % mod;
    }

    return ans;
}

int main() {
    freopen("card.in", "r", stdin);
    freopen("card.out", "w", stdout);
    scanf("%d", &n);

    for (int i = 1, x; i <= n; ++ i)
        scanf("%d", &x), ++ sum[x];

    for (int i = 1; i < M; ++ i)
        popcnt[i] = popcnt[i >> 1] + (i & 1);

    for (int i = 2; i <= N; ++ i) {
        if (!flag[i])
            prime[++ cnt] = i, pos[i] = cnt;

        for (int j = 1; j <= cnt && i * prime[j] <= N; ++ j) {
            flag[i * prime[j]] = true;

            if (!(i % prime[j]))
                break ;
        }
    }

    pw[0] = 1;

    for (int i = 1; i < SIZE; ++ i)
        pw[i] = 2ll * pw[i - 1] % mod;

    for (int i = 1; i <= cnt; ++ i)
        for (int k = 2; k <= N; ++ k) {
            if (k % prime[i])
                continue ;

            G[k].pb(i);

            if (i <= 13)
                buc[k] |= 1 << (i - 1);
        }

    for (int i = 0; i < M; ++ i) {
        for (int j = 2; j <= N; ++ j) {
            if (buc[j] & i)
                continue ;

            res[i] += sum[j];
            f[i][G[j].back()] += sum[j];
        }

        res[i] += sum[1];
    }

    scanf("%d", &m);
    std::vector<int> T;

    while (m --) {
        int c, x, form = 0, ans = 0, cur, cnt;
        scanf("%d", &c);
        T.clear();

        for (int i = 1; i <= c; ++ i) {
            scanf("%d", &x);
            T.pb(x);

            if (pos[x] <= 13)
                form |= 1 << (pos[x] - 1);
        }

        std::sort(T.begin(), T.end());

        for (int j = 0; j < M; ++ j) {
            if ((j | form) != form)
                continue ;

            cur = res[j];
            cnt = 1;

            for (int k = 0; k < c; ++ k) {
                if (T[k] < 43)
                    continue ;

                cnt = 1ll * cnt * (pw[f[j][pos[T[k]]]] + mod - 1) % mod;
                cur -= f[j][pos[T[k]]];
            }

            cnt = 1ll * cnt * pw[cur] % mod;

            if (popcnt[j] & 1)
                sub(ans, cnt);
            else
                add(ans, cnt);
        }

        printf("%d\n", ans);
    }

    return 0;
}
posted @ 2023-03-10 17:19  Skylakes  阅读(51)  评论(0编辑  收藏  举报