九校联考(DL24凉心模拟) 整除(中国剩余定理+原根性质)

题意简述

给定 n,m,求 n|xmx 在满足 x[1,n] 时合法的 x 的数量。答案模 998244353。单个测试点包含多组数据。
其中 n 由如下方式给出:

  • 给定 c 个不超过 t 的质数 pi(i[1,c]),有 n=i=1cpi

所有测试数据满足 1c50,1t104,1m109,数据组数 10000

题解

首先,由于已经给出了 n 的唯一分解式 n=p1p2pc,故我们可以将 n|xmx 这个式子拆成由 c 个形如 xmx (mod pi) 的同余方程组成的同余方程组。

我们先对第 i 个方程求出 [1,pi] 内满足条件的数的数量,记为 si。显然这些解 mod pi 均不相同,故 sixmod pi 意义下的合法余数的数量。由于所有 pi 均为互不相同的质数,即两两互质,因此可以证明,除了 1 为公共解外,任意两个方程的解([1,pi] 范围内)不存在交集。因此,我们在每个方程中任选一个解,构造一个新的一次同余方程组,共能得到 si 个不同的方程组。由于根据中国剩余定理,每个方程组在 [1,n] 范围内仅有唯一解,因此答案就为 si

所以,对于第 i 个方程,我们需要求 si=x=1p[xmx mod p]

存在一种做法是使用线性筛在接近线性的时间内求所有 [1,pi] 内的数的 m 次幂,然后暴力判断。不过这里,介绍一种更简单的方法。

首先给出以下结论:给定 mp,且 p 为质数,那么有:

(x=1p[xmx mod p])=gcd(m1,p1)+1

证明如下:

我们要求满足 x[1,p]xmx (mod p)x 的个数。

首先 x=p 一定满足,故不特殊考虑,最后答案 +1 即可。接下来只考虑 x[1,p1] 的情况。由于 p 为质数,因此 p 存在一个原根 g[1,p1] 内的任意数在 mod p 意义下都可以表示为 gy 的形式。这样,原方程就转化为:

gmygy (mod p)

根据费马小定理:

myy (mod p1)(m1)y0 (mod p1)

k=gcd(m1,p1),两边同时除以 k,得:

m1ky0 (mod p1k)

由于此时 gcd(m1k,p1k)=1,因此 y 一定有 p1k|y。由于 y[0,p2],显然,p1k 的任意小于 k 的非负整数倍(0k1 倍)均满足条件,因此 y 共有 k 种合法取值。

因此满足 x=1p[xmx mod p]i 共有 k+1 个。

这样,本题的时间复杂度就从 O(pi) 优化至了 O(c×logpi)loggcd 的复杂度)。

代码

线性筛求幂后暴力判断代码如下(常数莫名其妙很大...)

#include<bits/stdc++.h>
using namespace std;
#define rg register
const int e = 998244353, N = 1e4 + 10;
int mod, c, m;
inline void add(rg int& x, rg int y) {
x += y, x -= x >= mod ? mod : 0;
}
inline void mul(rg int& x, rg int y) {
x = 1ull * x * y % mod;
}
inline int qpow(rg int v, rg int p) {
rg int res = 1;
for (; p; p >>= 1, mul(v, v)) {
if (p & 1) {
mul(res, v);
}
}
return res;
}
inline int solve(rg int n) {
rg int p[N], f[N], pri[N], t;
mod = n, t = 0;
fill(p + 1, p + 1 + n, 1);
rg int res = 2;
for (rg int i = 2; i < n; ++i) {
if (p[i]) {
pri[++t] = i, f[i] = qpow(i, m);
}
res += (i == f[i]);
for (rg int j = 1, d; j <= t && (d = i * pri[j]) <= n; ++j) {
p[d] = 0;
f[d] = 1ull * f[i] * f[pri[j]] % mod;
if (i % pri[j] == 0) {
break;
}
}
}
return res;
}
int main() {
int T; scanf("%*d%d", &T);
for (rg int kase = 1; kase <= T; ++kase) {
scanf("%d%d", &c, &m);
rg int ans = 1;
for (rg int i = 1; i <= c; ++i) {
rg int x; scanf("%d", &x);
rg int v = solve(x);
mod = e, mul(ans, v);
}
printf("%d\n", ans);
}
return 0;
}

用更简单的方法代码如下:

#include<bits/stdc++.h>
using namespace std;
#define rg register
const int mod = 998244353;
inline void mul(int& x, int y) {
x = 1ll * x * y % mod;
}
int main() {
int T; scanf("%*d%d", &T);
for (rg int kase = 1; kase <= T; ++kase) {
int n, m; scanf("%d%d", &n, &m);
int ans = 1;
for (rg int i = 1; i <= n; ++i) {
int x; scanf("%d", &x);
mul(ans, __gcd(m - 1, x - 1) + 1);
}
printf("%d\n", ans);
}
return 0;
}
posted @   ImagineC  阅读(1110)  评论(1编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示