P5366 [SNOI2017] 遗失的答案

P5366 [SNOI2017] 遗失的答案 题解

题意

给定 \(n, L, G\),有 \(Q\) 组询问,每次给出一个 \(x\),求有多少的 \(\{1, 2, \dots, n\}\) 的子集,满足:

  • 所有元素的最大公约数为 \(G\)
  • 所有元素的最小公倍数为 \(L\)
  • 集合包含 \(x\)

\(n, x, L, G \le 10^8, Q \le 10^5\)

题解

好像和题解做法都不太一样?

首先把 \(n, G, L, x\) 都除以 \(G\),除去后整个结构是不变的,于是只需考虑 \(G = 1\)

考虑刻画最小公倍数与最大公倍数。

不难想到从质因数分解的角度刻画,而简单一算,发现 \(10^8\) 以内的数最多有 \(8\) 个质因子。

另外,我们选出的数一定为 \(L\) 的约数,而 \(10^8\) 以内的数约数最多的有大概 \(700\) 个。

假如没有包含 \(x\) 的条件,只要求方案数会怎么样?

\(L\) 的约数集合 \(g\),同时设 \(L\)\(m\) 个质因子。

只需取以 \(S \subseteq g\),且对于 \(L\) 的一个质因子 \(p\),存在 \(x \in S\),满足 \(v(p, x) = v(p, L)\),存在 \(y \in S\),满足 \(v(p, y) = 0\)

于是由于只有 \(m\) 个质因子,我们就可以用一个 \(2m\) 位的数压下我们需要知道的所有信息。

我们记 \(F(A)\) 表示 \(A\) 的状态。

那么我们只需求 \([x^{2^{2m}-1}](1+x^{F(g_1)})(1+x^{F(g_2)})\dots(1+x^{F(g_k)})\)

其中我们乘法是或卷积。

接下来我们考虑要求包含 \(x\) 的情况,介于 \(x\) 最多大概 \(700\) 种非零,我们不妨全部预处理出来。

我们知道,求或卷积时,我们先做DWT,然后点乘,在做IDWT。

在本题中,记 \(B = (1+x^{F(g_1)})(1+x^{F(g_2)})\dots(1+x^{F(g_{i-1})})(1+x^{F(g_{i+1})})\dots(1+x^{F(g_k)})\)

由于 \(DWT(1 + x^k)\) 这样的式子每一项都属于 \(\{1, 2\}\),于是 \(DWT(B)\) 完全可以在 \(2^m\) 的时间内算出来。

然后简单推一下式子,记 \(C = DWT(B)\),我们要求:

\[\begin{align} \sum_{I \supseteq \overline{F(A_i)}} B_i & = \sum_{I \supseteq \overline{F(A_i)}} \sum_{J \subseteq I} C_J (-1)^{|I| - |J|} \\ & = \sum_{J} C_J(-1)^{|J|} \sum_{I \supseteq \overline{(F(A_i))} \cup I} (-1)^{J} \\ & = \sum_{J \supseteq F(A_i)} C_J(-1)^{|J|} \end{align} \]

可以在 \(\mathcal O(2^{2m})\) 内算出来,于是总时间是 \(\mathcal O(|g|2^{2m})\),大约在 \(5 \times 10^7\) 左右,可以接受。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int K = 1005, N = (1 << 16);
const LL P = 1e9 + 7, iv2 = (P + 1) >> 1;
int n, G, L, cnt[N];
int num[K], tot, a[K], cur[K], id, st[K]; LL pw[N];
unordered_map<int, int> ans;
int v(int p, int n) { int cnt = 0; while (n % p == 0) n /= p, cnt++; return cnt; }
void init(int g) {
    for (int i = 1; i * i <= g; i++)
        if (g % i == 0) { num[++tot] = i; if (i * i < g) num[++tot] = g / i; }
    sort(num + 1, num + tot + 1);
    while (num[tot] > n) tot--;
    for (int i = 2; i * i <= g; i++)
        if (g % i == 0) { a[++id] = i; while (g % i == 0) cur[id]++, g /= i; }
    if (g != 1) a[++id] = g, cur[id] = 1;
    pw[0] = 1;
    for (int i = 1; i <= tot; i++) {
        pw[i] = pw[i - 1] * 2 % P;
        for (int j = 1; j <= id; j++) st[i] = (st[i] << 1) | (cur[j] == v(a[j], num[i]));
        for (int j = 1; j <= id; j++) st[i] = (st[i] << 1) | (0 == v(a[j], num[i]));
    }
    for (int i = 1; i < (1 << (2 *id)); i++)
        cnt[i] = cnt[i - (i & -i)] + 1;
}
LL A[N], B[N];
void IDMT(LL *A, int n) {
    for (int i = 1; i < (1 << n); i *= 2)
        for (int j = 0; j < (1 << n); j += i << 1)
            for (int k = 0; k < i; k++)
                (A[i + j + k] += P - A[j + k]) %= P;
}
int main() {
    scanf("%d%d%d", &n, &G, &L);
    if (L % G != 0) { int q; scanf("%d", &q); for (; q--; ) puts("0"); return 0; }
    n /= G, L /= G, init(L);
    for (int i = 1; i <= tot; i++)
        for (int j = 0; j < 1 << (2 * id); j++)
            A[j] += (st[i] | j) == j;
    for (int i = 1; i <= tot; i++) {
        for (int j = 0; j < 1 << (2 * id); j++)
            B[j] = A[j] - ((st[i] | j) == j), B[j] = pw[B[j]];
        LL res = 0;
        for (int j = 0; j < (1 << 2 * id); j++)
            if ((j | st[i]) == j) res += B[j] * (cnt[j] & 1 ? (-1) : (1)), res = (res + P) % P;
        ans[num[i]] = res;
    }
    int q; scanf("%d", &q);
    for (int x; q--; ) scanf("%d", &x), printf("%d\n", x % G == 0 ? ans[x / G] : 0);
    return 0;
}
posted @ 2026-07-04 11:44  AC_protoss  阅读(3)  评论(0)    收藏  举报