P5366 [SNOI2017] 遗失的答案 分析
题目概述
题目链接:https://www.luogu.com.cn/problem/P5366。
有编号为 \(1\) 到 \(n\) 的物品,小 \(X\) 忘记了自己选的什么物品,它只记得他选的部分的物品的编号最大公约数为 \(g\),最小公倍数为 \(l\)。现在给你 \(q\) 个询问,每个询问一个 \(x\),问你当必选 \(x\) 的时候满足条件有多少种方案。
数据范围:\(1\leq n,g,l,x\leq 10^8,1\leq q \leq 10^5\)。
分析
调了一个晚上。(注意赋值顺序)
首先 \(d\mid x,d\mid l\),那么我们可以直接让 \(x,l,n\) 对 \(g\) 整除,注意这里 \(n\) 可以对 \(g\) 整除(显然)。
现在问题变成了新的 \(n,g,l\),其中 \(g\) 为 \(1\)。
那么我们就变成了 \(x\mid l\)。
我们选取这些编号取最小公倍数相当于对每个质因子的次幂的最大值(套路),而最大公约数是取最小值。
那么显然的,我们先对 \(l\) 质因数分解。
不难发现,这里质因数的个数不超过 \(8\)(即 \(\omega(10^8)=8\))。
我们发现这个最大值你只需要知道是否到达 \(r_i\) 即可(其中 \(r_i\) 表示 \(l\) 第 \(i\) 个质因数的次幂)。
那么我们就可以愉快地状态压缩了:设前 \(8\) 位表示有没有第 \(i\) 个质因数次幂是 \(0\)(这个必须要知道因为要知道最大公约数为 \(1\)),后 \(8\) 位表示有没有第 \(i\) 个质因数次幂是不是 \(r_i\)。(本题关键)
那么不考虑 \(x\) 的限制,我们的答案就是取若干个数,其状态或起来为全集的方案数,将或起来答案为 \(S\) 的方案数量记为 \(f_S\)。
设 \(g_S\) 表示或起来为 \(S\) 的子集的方案,那么显然:
容斥一下:
那么对于全集 \(U\),其 \(|U|\) 是偶数,因此有:
对于 \(g\),我们还有:
其中 \(c(S)\) 表示集合 \(S\) 的子集的数量,这个显然可以用高维前缀和解决。
现在考虑有 \(x\) 的限制。
那么我们 \(f,g\) 的定义也改变为要 \(x\) 的,所以:
但是我们的 \(f\) 与 \(g\) 的关系不变,也就是说我们的答案还是:
得到:
把 \(x\) 提出来就是:
显然这就是求个超集和,还是可以用 FWT 或者高维前缀和做。
代码
时间复杂度 \(\mathcal{O}(\omega(L)(d(L)+2^{2\omega(L)})+n)\)。
#include <iostream>
#include <stdlib.h>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define int long long
#define N 10
using namespace std;
const int mod = 1e9 + 7;
int qpow(int a,int b) {
int res = 1;
while(b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
int n,g,l,r[N];
vector<int> fac;
int f[(1 << 16) + 100];
int get(int x) {
int sta = 0;
for (int i = 0;i < fac.size();i ++) {
int c = 0;
while(x % fac[i] == 0) x /= fac[i],c ++;
if (c == 0) sta |= (1 << fac.size() + i);
if (c == r[i]) sta |= (1 << i);
}
return sta;
}
int popcount(int x) {
int res = 0;
for (;x;x -= x & -x) res ++;
return res;
}
signed main(){
cin >> n >> g >> l;
int G = g,L = l;
if (l % g) {
int q;
cin >> q;
for (;q --;) puts("0");
return 0;
}
n /= g,l /= g,g = 1;
int tp = l;
// cout << tp << '\n';
for (int i = 2;i * i <= tp;i ++)
if (tp % i == 0) {
fac.push_back(i);
while(tp % i == 0) r[fac.size() - 1] ++,tp /= i;
}
if (tp > 1) fac.push_back(tp),r[fac.size() - 1] ++;
// for (auto i : fac) cout << i << ' ';
for (int i = 1;i <= n;i ++)
if (l % i == 0) f[get(i)] ++;
int S = (1 << (fac.size() << 1));
// for (int i = 0;i < S;i ++) cout << f[i] << ' ';
// cout << '\n';
int len = (fac.size() << 1);
for (int i = 0;i < len;i ++)
for (int j = 0;j < S;j ++)
if ((j >> i) & 1) f[j] = (f[j] + f[j ^ (1 << i)]) % mod;
// for (int i = 0;i < S;i ++) cout << f[i] << ' ';
// cout << '\n';
// cout << popcount(5) << '\n';
for (int i = 0;i < S;i ++)
if (f[i]) {
int w = (popcount(i) & 1) ? mod - 1 : 1;
f[i] = qpow(2,f[i] - 1) * w % mod;
}
// for (int i = 0;i < S;i ++) cout << f[i] << ' ';
// cout << '\n';
for (int i = 0;i < len;i ++)
for (int j = 0;j < S;j ++)
if (!((j >> i) & 1)) f[j] = (f[j] + f[j | (1 << i)]) % mod;
// for (int i = 0;i < S;i ++) cout << f[i] << ' ';
// cout << '\n';
int q;
cin >> q;
for (;q--;) {
int x;
scanf("%lld",&x);
if (x % G) {
puts("0");
continue;
}
x /= G;
if (l % x || x > n) {
puts("0");
continue;
}
printf("%lld\n",f[get(x)]);
}
return 0;
}
/*
5 1 30
5
1 2 3 4 5
*/

浙公网安备 33010602011771号