繁凡出品的全新系列:解题报告系列 —— 超高质量算法题单,配套我写的超高质量题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数( 1 ∼ 5 1 \sim 5 1∼5),以模板题难度 1 1 1 为基准。
这样大家在学习算法的时候就可以执行这样的流程:
%
阅读我的【学习笔记】 / 【算法全家桶】学习算法 ⇒ \Rightarrow ⇒ 阅读我的相应算法的【解题报告】获得高质量题单 ⇒ \Rightarrow ⇒ 根据我的一句话题解的提示尝试自己解决问题 ⇒ \Rightarrow ⇒ 点开我的详细题解链接学习巩固(好耶)
%
题单链接:【解题报告】快速沃尔什变换FWT(ICPC / CCPC / NOIP / NOI / CF / AT / NC / P / BZOJ)超高质量题解 !!!
C、(牛客练习赛41 F)简单数学题(数论 + FWT)(3.5)
Link
https://ac.nowcoder.com/acm/contest/373/F
Problem
定义如下函数:
f
(
x
)
=
{
1
x
=
1
max
{
t
∣
x
m
o
d
t
=
0
&
&
∣
μ
(
t
)
∣
=
1
}
x
≥
2
\ f(x)= \begin{cases} 1 & {x=1}\\ \max\{ t\ |\ {x \bmod t = 0}\ \&\&\ |\mu(t)| = 1 \}& {x \geq 2} \end{cases}
f(x)={1max{t ∣ xmodt=0 && ∣μ(t)∣=1}x=1x≥2
其中 μ ( t ) \mu(t) μ(t)为莫比乌斯函数,详见:https://en.wikipedia.org/wiki/M%C3%B6bius_function
对于任何整数 x \ x x,若有 x = p 1 n 1 p 2 n 2 . . . p k n k x = p_{1}^{n_{1}}p_{2}^{n_{2}}...p_{k}^{n_{k}} x=p1n1p2n2...pknk ,其中 p i \ p_{i} pi是 x \ x x 第 i \ i i 大的质因子,则
g ( x ) = p 1 β ( n 1 ) p 2 β ( n 2 ) . . . p k β ( n k ) g(x) = p_{1}^{\beta(n_{1})}p_{2}^{\beta(n_{2})}...p_{k}^{\beta(n_{k})} g(x)=p1β(n1)p2β(n2)...pkβ(nk)
β ( x ) = { 0 x is even 1 x is odd \beta(x)= \begin{cases} 0& \text{x is even}\\ 1& \text{x is odd} \end{cases} β(x)={01x is evenx is odd
现在定义 F ( a , b , c ) = g ( f ( a ) ∗ f ( b ) ∗ f ( c ) ) \ F(a, b, c) = g(f(a)*f(b)*f(c)) F(a,b,c)=g(f(a)∗f(b)∗f(c))
并给出三个长度均为 n \ n n 的数列
A 1 A 2 A 3 . . . A n \ A_{1}A_{2}A_{3}...A_{n} A1A2A3...An
B 1 B 2 B 3 . . . B n \ B_{1}B_{2}B_{3}...B_{n} B1B2B3...Bn
C 1 C 2 C 3 . . . C n \ C_{1}C_{2}C_{3}...C_{n} C1C2C3...Cn
对于某个整数 z \ z z,如有 z = F ( A i , B j , C k ) ( 1 ≤ i , j , k ≤ n ) \ z = F(A_{i}, B_{j}, C_{k})(1 \leq i,j,k \leq n) z=F(Ai,Bj,Ck)(1≤i,j,k≤n)
则称 G F ( i , j , k ) \ GF(i, j, k) GF(i,j,k)为z的一种表示
设 S \ S S 集合为 S = { z ∣ z = F ( A i , B j , C k ) , 1 ≤ i , j , k ≤ n } S = \{ z|z=F(A_{i}, B_{j}, C_{k}),1 \leq i,j,k \leq n \} S={z∣z=F(Ai,Bj,Ck),1≤i,j,k≤n} , ∣ Z ∣ \ {|Z|} ∣Z∣为 S \ S S 中元素的个数
求 a n s = ∑ i = 1 ∣ Z ∣ z i k i ans = \sum_{i=1}^{|Z|}{z_{i} k_{i}} ans=∑i=1∣Z∣ziki ,其中 z i z_{i} zi 是集合 S \ S S 的第 i \ i i 个元素,其中 k i k_{i} ki 为 z i z_{i} zi 的不同的表示的个数。答案对 1 0 9 + 7 \ 10^{9}+7 109+7 取模。
注意:对于一个数 z \ z z ,如果 z = F ( A i 1 , B j 1 , C k 1 ) ) = F ( A i 2 , B j 2 , C k 2 ) ) \ z = F(A_{i_{1}}, B_{j_{1}}, C_{k_{1}})) = F(A_{i_{2}}, B_{j_{2}}, C_{k_{2}})) z=F(Ai1,Bj1,Ck1))=F(Ai2,Bj2,Ck2)) ,只要 i 1 ≠ i 2 o r j 1 ≠ j 2 o r k 1 ≠ k 2 i_{1}\neq i_{2}\ or\ j_{1}\neq j_{2}\ or\ k_{1}\neq \ k_{2} i1=i2 or j1=j2 or k1= k2 ,那么我们说 z z z 的这两种表示的两种不同的表示。
1 ≤ n ≤ 1 0 5 , A i , B i , C i ≤ 1 0 18 1\le n\le 10^5,A_i,B_i,C_i\le10^{18} 1≤n≤105,Ai,Bi,Ci≤1018,保证后三行输入的所有整数每个数的最大质因子不会超过 71 \ 71 71。
Sample Input
2
2 3
4 6
6 10
Sample Output
72
Hint
一句话题解: 看上去比较麻烦,但是搞清楚每个函数的作用以后一步步分析即可。关键还是在于如何引出异或操作,我们的 f f f 函数实际上得到的是一些质数的一次幂的乘积,而题目中告诉我们了所含质因数 ≤ 71 \le 71 ≤71 ,所以我们可以预处理一下,那么一共就只有 20 个数了,那么我们的 f f f 函数的实际意义就变成了从 20 个质数中选择若干个质数,那么对于每一个质数来说就只有选或者不选两种情况,所以可以用状态压缩,压成一个二进制数,这样我们就可以使用位运算了 ~ 能想到这一点就会发现这几乎就是一个 FWT 的模板题了,再往后就是正常的变换了,同样的我们只需要想办法将进行位运算的数变成下标即找到一个下标的属性的序列,将他们卷起来求方案数 k i k_i ki 最后求 ans 即可。
Solution
题目比较麻烦,一步一步来分析。
首先,分析 f ( x ) f(x) f(x), f ( x ) f(x) f(x) 求的是一个最大的 t t t ,满足 t t t 是 x x x 的约数,且 t t t 是质数一次幂的乘积。最大,所以肯定是所有质因子的乘积。
由于题目中规定质因数不会超过 71 71 71 ,所以我们可以先预处理出 2 2 2 到 71 71 71 的质数表,一共只有 20 20 20 个,我们输入的数组,先将输入的每个数组处理成 f ( a [ i ] ) f(a[i]) f(a[i]),因为我们只需要知道选了哪些质数就行了,也就意味着每个质数只有 0 0 0 或者 1 1 1 两种选择,我们得到的 f ( x ) f(x) f(x) 的值就可以表示成从质数表里挑选一些数的乘积,这样我们的 f ( x ) f(x) f(x) 就可以表示成将 x x x 质因数分解后选择质数表里的某些质数的下标压缩成一个二进制数 m a s k mask mask 来表示这个状态 f ( x ) f(x) f(x),也就是状态压缩,一共只有 2 20 = 1 e 6 2^{20} = 1e6 220=1e6。
例如
x
=
10
=
2
×
5
x=10=2\times 5
x=10=2×5,即选择第
0
0
0 个质数
2
2
2 ,第
2
2
2 个质数
5
5
5 ,所以
m
a
s
k
=
101
=
5
mask=101=5
mask=101=5。则 num[mask] ++ ;
,其中 num 表示一个质数集,++ 意味着这个
x
x
x 属于这个质数集,可表示成这个质数集的数的个数 +1。
然后分析 g ( x ) g(x) g(x),结合 β ( x ) \beta(x) β(x) 和 F ( a , b , c ) F(a,b,c) F(a,b,c) 发现实际上就是将 f ( a ) , f ( b ) , f ( c ) f(a),f(b),f(c) f(a),f(b),f(c) 三个数的相同质因数的次幂( 0 或者 1 )的异或值作为新次幂的数的乘积。而我们的 f ( a ) , f ( b ) , f ( c ) f(a),f(b),f(c) f(a),f(b),f(c) 已经是被处理成了二进制的形式的数,所以直接异或就行了。
这样问题就转变成了处理素数集 f ( x ) f(x) f(x) 之间的关系也就是集合与集合之间的异或操作,也就是经典 快速沃尔什变换(FWT),FWT 可以将两个集合按照下标卷起来。
因为我们要统计的是方案数 k i k_i ki, 而显然 z z z 的出现次数 k z = n u m a × n u m b × n u m c k_z=num_a\times num_b\times num_c kz=numa×numb×numc,其中 a ⊕ b ⊕ c = z a\oplus b\oplus c=z a⊕b⊕c=z。
即:
k z = ∑ z = a ⊕ b ⊕ c n u m a × n u m b × n u m c k_z=\sum_{z=a\oplus b\oplus c}num_a\times num_b\times num_c kz=z=a⊕b⊕c∑numa×numb×numc
实际意义就是 a i a_i ai 的个数是 n u m a i num_{a_i} numai, b i b_i bi 的个数是 n u m b i num_{b_i} numbi,那么 a i a_i ai 和 b i b_i bi 异或起来组成的 z z z 的 方案数 即题目中要求的不同的表示的个数 k i k_i ki 就是 n u m a i × n u m b i num_{a_i}\times num_{b_i} numai×numbi。
求出 k i k_i ki ,答案为 a n s = ∑ i = 1 ∣ Z ∣ z i k i ans = \sum_{i=1}^{|Z|}{z_{i} k_{i}} ans=∑i=1∣Z∣ziki,我们枚举状压的那个二进制数,将枚举到的 i i i 还原为 z z z ,加上 z × k i z\times k_i z×ki (没有这个 z z z 的话 k i = 0 k_i=0 ki=0 不影响)即可。
是不是很简单
Code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
typedef int itn;
const int N = (1 << 20) + 7, mod = 1e9 + 7;
const ll INF = 4e18;
itn dcnt;
#define de(x) cout << x << " x" << endl
#define de1() cout << ++ dcnt << "ok" << endl
itn n, m;
ll a[N], b[N], c[N], k[N];
ll primes[N], cnt_primes;
int cnt;
bool vis[N];
void init()
{
for(int i = 2; i <= 71; ++ i) {
if(vis[i] == 0) primes[cnt_primes ++ ] = 1ll * i;
for(int j = 0; j < cnt_primes && primes[j] * i <= 71; ++ j) {
vis[i * primes[j]] = true;
if(i % primes[j] == 0) break;
}
}
}
ll qpow(ll a, ll b)
{
ll res = 1;
while(b) {
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res % mod;
}
int inv2 = qpow(2, mod - 2);
void XOR(ll *f, int n, int x = 1)
{
for(int o = 2; o <= n; o <<= 1) {
for(int i = 0, k = o >> 1; i < n; i += o) {
for(int j = 0; j < k; ++ j) {
ll X = f[i + j];
ll Y = f[i + j + k];
f[i + j] = (1ll * X + Y) % mod;
f[i + j + k] = (1ll * (X - Y) % mod + mod) % mod;
if(x == -1) {
f[i + j] = (1ll * f[i + j] * inv2) % mod;
f[i + j + k] = (1ll * f[i + j + k] * inv2) % mod;
}
}
}
}
}
void solve(ll *a)//求 f(a)
{
ll x;
for(int i = 1; i <= n; ++ i) {
scanf("%lld", &x);
ll cnt = 0, mask = 0;
for(int j = 0; j < cnt_primes && x > 1; ++ j) {
cnt = 0;
ll p = primes[j];
while(x % p == 0) {
x /= p;
cnt ++ ;
}
if(cnt)
mask |= 1 << j;//选择了 j 这个数
}
a[mask] ++ ;
}
}
ll cal(int x)
{
ll res = 1;
for(int i = 0; i < cnt_primes; ++ i) {
if(x & (1 << i)) res = 1ll * res * primes[i] % mod;
}
return res;
}
int main()
{
init();
scanf("%d", &n);
solve(a), solve(b), solve(c);
XOR(a, 1 << 20), XOR(b, 1 << 20), XOR(c, 1 << 20);
for(int i = 0; i < 1 << 20; ++ i)
k[i] = 1ll * a[i] * b[i] % mod * c[i] % mod;
XOR(k, 1 << 20, -1);
ll ans = 0;
for(int i = 0; i < 1 << 20; ++ i) {
ans = (1ll * ans + cal(i) * k[i] % mod) % mod;
}
printf("%lld\n", ans);
return 0;
}