[Nowcoder 2018ACM多校第十场E] Rikka with Equation

本文探讨了模线性方程组解数的计算问题,利用中国剩余定理和欧拉函数,提出了一个高效的算法解决方案。通过将模数分解为质数幂,并考虑每个质数幂下的解数,最终合并得到总解数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:
给出一组系数A, 和模数m
定义f(A,m)f(A,m)为模线性方程aixi=0 (mod m)∑ai∗xi=0 (mod m) 的解数
给出一个长度为n的数组B, 对于B的所有非空子集A, 和一个属于[1,M]的mwm=f(A,m) mod 998244353wm=∑f(A,m) mod 998244353, 求所有wmwm的异或和。(n,m,Bi105)(n,m,Bi≤105)

题目思路:
先证明f(A,m)=gcd(A,m)m|A|1f(A,m)=gcd(A,m)∗m|A|−1
考虑中国剩余定理, 先将m拆成质数幂的形式, 考虑对于某个单独质数幂的答案, 则原方程的解数就是各维度下解数的乘积。
考虑ptpt, p是质数, aixi=0 (mod pt)∑ai∗xi=0 (mod pt)
由于p是质数, 对于aiai种与p互质的部分可以直接用逆元消去, 考虑aiai剩下的部分是pqipqi
原方程变为pqixi=0 (mod pt)∑pqi∗xi=0 (mod pt)
假设pqipqi种最小的是pq1pq1, 将其余的部分看成一个整体CC
pq1x1+C=0 (mod pt)
仅考虑x1x1的取值, 方程有解的条件是C | gcd(pq1,pt)C | gcd(pq1,pt), 由于pq1pq1pqipqi中最小的那个, 所以条件成立, 方程有解, 解数就是gcd(pq1,pt)gcd(pq1,pt)pq1pq1, 然后剩下的x的值可以随便取有ptpt种取值
所以在ptpt下的解数是pqi(qt)|A|1pqi∗(qt)|A|−1, 其中pqipqi是所有aiai中拥有质数pp的最小指数。
在用中国剩余定理合并一下答案, 可得f(A,m)=gcd(A,m)m|A|1
现在要求

mAgcd(A,m)m|A|1∑m∑Agcd(A,m)∗m|A|−1

然后使用套路1: 这种gcd求和, 先预处理wdwd, 表示所有gcd是d的倍数情况下的和
要让gcd是d的倍数, 则gcd中的每一个元素都得是d的倍数, 预处理出数组CdCd表示B数组中有多少个数是d的倍数, 则
wd=d|mi=1Cdmi1=d|m1m(i=0Cdmi1)=d|m1m((m+1)Cd1)wd=∑d|m∑i=1Cdmi−1=∑d|m1m(∑i=0Cdmi−1)=∑d|m1m∗((m+1)Cd−1)

接着使用套路2: 容斥系数
然而求出了这个之后, 直接将wdwd相加很明显不是答案, 一是wdwd中对于每个gcd贡献都算作1, 实际上应该是gcd的值, 二是wdwd考虑的是所有gcd是d的倍数的情况, 直接相加有很多重复。 然而我们还是想直接通过wdwd的某种求和方式来表达我们相求的式子
考虑每个wdwd前都乘上一个系数adad
我们想要adwd∑ad∗wd就是答案, 则根据定义, adad需要满足
d|nad=n∑d|nad=n

然后我们发现这个性质和ϕ(d)ϕ(d)惊人的相似, 于是我就直接使用欧拉函数。
所以最终算的就是
dd|mϕ(d)1m((m+1)Cd1)∑d∑d|mϕ(d)∗1m∗((m+1)Cd−1)

由于最后要求的是一个异或和, 把对每个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;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值