题目大意:
给出一组系数A, 和模数m
定义
f(A,m)
f
(
A
,
m
)
为模线性方程
∑ai∗xi=0 (mod m)
∑
a
i
∗
x
i
=
0
(
m
o
d
m
)
的解数
给出一个长度为n的数组B, 对于B的所有非空子集A, 和一个属于[1,M]的m
wm=∑f(A,m) mod 998244353
w
m
=
∑
f
(
A
,
m
)
m
o
d
998244353
, 求所有
wm
w
m
的异或和。
(n,m,Bi≤105)
(
n
,
m
,
B
i
≤
10
5
)
题目思路:
先证明
f(A,m)=gcd(A,m)∗m|A|−1
f
(
A
,
m
)
=
g
c
d
(
A
,
m
)
∗
m
|
A
|
−
1
考虑中国剩余定理, 先将m拆成质数幂的形式, 考虑对于某个单独质数幂的答案, 则原方程的解数就是各维度下解数的乘积。
考虑
pt
p
t
, p是质数,
∑ai∗xi=0 (mod pt)
∑
a
i
∗
x
i
=
0
(
m
o
d
p
t
)
由于p是质数, 对于
ai
a
i
种与p互质的部分可以直接用逆元消去, 考虑
ai
a
i
剩下的部分是
pqi
p
q
i
原方程变为
∑pqi∗xi=0 (mod pt)
∑
p
q
i
∗
x
i
=
0
(
m
o
d
p
t
)
假设
pqi
p
q
i
种最小的是
pq1
p
q
1
, 将其余的部分看成一个整体
C
C
仅考虑
x1
x
1
的取值, 方程有解的条件是
C | gcd(pq1,pt)
C
|
g
c
d
(
p
q
1
,
p
t
)
, 由于
pq1
p
q
1
是
pqi
p
q
i
中最小的那个, 所以条件成立, 方程有解, 解数就是
gcd(pq1,pt)
g
c
d
(
p
q
1
,
p
t
)
即
pq1
p
q
1
, 然后剩下的x的值可以随便取有
pt
p
t
种取值
所以在
pt
p
t
下的解数是
pqi∗(qt)|A|−1
p
q
i
∗
(
q
t
)
|
A
|
−
1
, 其中
pqi
p
q
i
是所有
ai
a
i
中拥有质数
p
p
的最小指数。
在用中国剩余定理合并一下答案, 可得
现在要求
然后使用套路1: 这种gcd求和, 先预处理 wd w d , 表示所有gcd是d的倍数情况下的和
要让gcd是d的倍数, 则gcd中的每一个元素都得是d的倍数, 预处理出数组 Cd C d 表示B数组中有多少个数是d的倍数, 则
接着使用套路2: 容斥系数
然而求出了这个之后, 直接将 wd w d 相加很明显不是答案, 一是 wd w d 中对于每个gcd贡献都算作1, 实际上应该是gcd的值, 二是 wd w d 考虑的是所有gcd是d的倍数的情况, 直接相加有很多重复。 然而我们还是想直接通过 wd w d 的某种求和方式来表达我们相求的式子
考虑每个 wd w d 前都乘上一个系数 ad a d
我们想要 ∑ad∗wd ∑ a d ∗ w d 就是答案, 则根据定义, ad a d 需要满足
然后我们发现这个性质和 ϕ(d) ϕ ( d ) 惊人的相似, 于是我就直接使用欧拉函数。
所以最终算的就是
由于最后要求的是一个异或和, 把对每个m的贡献单独存起来, 最后求一遍异或和即可。
#include <map>
#include <cmath>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define ull unsigned ll
#define db double
#define fi first
#define se second
#define pi pair<int, int >
#define ls (x << 1)
#define rs ((x << 1) | 1)
#define mid ((l + r) >> 1)
#define mp(x, y) make_pair((x), (y))
#define pb push_back
#define sqr(x) ((x) * (x))
#define eps 1e-8
using namespace std;
const int N = (int)1e5 + 10;
const int mo = 998244353;
int tot, phi[N], prim[N]; bool ok[N];
void getPhiAndPrime(){
phi[1] = 1;
for (int i = 2; i < N; i ++){
if (!ok[i]){ok[i] = i; prim[++ tot] = i; phi[i] = i - 1;}
for (int j = 1; j <= tot; j ++){
if (1ll * i * prim[j] >= N) break;
ok[i * prim[j]] = 1;
if (i % prim[j]) phi[i * prim[j]] = phi[i] * (prim[j] - 1);
else {phi[i * prim[j]] = phi[i] * prim[j]; break;}
}
}
}
ll pw(ll x, int k){
ll ret = 1;
while (k){
if (k & 1) ret = ret * x % mo;
x = x * x % mo;
k >>= 1;
}
return ret;
}
int n, m; ll A[N], B[N], C[N], ans[N];
int main(){
getPhiAndPrime();
int T; scanf("%d", &T);
while (T --){
memset(A, 0, sizeof(A));
memset(C, 0, sizeof(C));
memset(ans, 0, sizeof(ans));
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i ++){
scanf("%lld", B + i);
A[B[i]] ++;
}
for (int i = 1; i <= m; i ++)
for (int j = i; j < N; j += i)
C[i] += A[j];
for (int i = 1; i <= m; i ++)
for (int j = i; j <= m; j += i)
(ans[j] += phi[i] * pw(j, mo - 2) % mo * (pw(j + 1, C[i]) - 1) % mo) %= mo;
ll ret = 0;
for (int i = 1; i <= m; i ++) ret ^= ans[i];
printf("%lld\n", ret);
}
return 0;
}