Description
- 给定 n , m n,m n,m, 求 [ 1 , n ] [1,n] [1,n] 内满足 n ∣ x m − x n|x^m-x n∣xm−x 的 x x x 的个数。
- m ≤ 1 0 9 m\le 10^9 m≤109。
- n = ∏ i = 1 c p i n=\prod_{i=1}^c p_i n=∏i=1cpi, p i p_i pi 均为质数且互不相同, p i ≤ 10000 , c ≤ 50 p_i\le 10000,c\le50 pi≤10000,c≤50。
- 每个测试点数据组数 ≤ 10000 \le 10000 ≤10000。
- 时间限制 5 s 5s 5s,空间限制 256 M B 256MB 256MB。
Solution
- 非常有意思的一道题。
- 感觉让我这个初学数论的菜鸡学到了很多东西。
算法一
- 原方程等价于 x ( x m − 1 − 1 ) ≡ 0 ( m o d n ) x(x^{m-1}-1)\equiv 0(mod~n) x(xm−1−1)≡0(mod n)
- x x x 的取值范围可以对应到 [ 0 , n ) [0,n) [0,n),这样我们只需要判断模 n n n 意义下的解的个数即可。
- 这个同余方程成立,等价于对于每个 i i i x ( x m − 1 − 1 ) ≡ 0 ( m o d p i ) x(x^{m-1}-1)\equiv 0(mod~p_i) x(xm−1−1)≡0(mod pi)
- 由于 p i p_i pi 是质数,我们可以知道上面这个方程又等价于 x ≡ 0 ( m o d p i ) x\equiv 0(mod~p_i) x≡0(mod pi) 或 x m − 1 ≡ 1 ( m o d p i ) x^{m-1}\equiv 1(mod~p_i) xm−1≡1(mod pi)
- 上面两个方程解出来的 x x x 均是 p i p_i pi 的剩余类。
- 那么我们就取模 p p p 的值作为方程的解。
- 显然两个方程解出的 x x x 不会有同一个。
- 所以方程 x ( x m − 1 − 1 ) ≡ 0 ( m o d p i ) x(x^{m-1}-1)\equiv 0(mod~p_i) x(xm−1−1)≡0(mod pi) 的解数就是上面两个方程的解数之和。
- 我们假设 A i A_i Ai 表示我们对方程 x ( x m − 1 − 1 ) ≡ 0 ( m o d p i ) x(x^{m-1}-1)\equiv 0(mod~p_i) x(xm−1−1)≡0(mod pi) 解出的所有 x x x 模 p i p_i pi 的值组成的集合。
- 那么,因为 p i p_i pi 互不相同,根据中国剩余定理,我们对于每个同余方程组:
∀ i ∈ [ 1 , c ] , x ≡ a i ( m o d p i ) , a i ∈ A i \forall i\in[1,c],x\equiv a_i(mod~p_i),a_i\in A_i ∀i∈[1,c],x≡ai(mod pi),ai∈Ai
- 我们都能得到模 n n n 意义下的唯一解。
- 所以解数就是所有集合 A i A_i Ai 的大小的乘积。
- 我们回到刚刚的两个方程: x ≡ 0 ( m o d p i ) x\equiv 0(mod~p_i) x≡0(mod pi) 或 x m − 1 ≡ 1 ( m o d p i ) x^{m-1}\equiv 1(mod~p_i) xm−1≡1(mod pi)
- 对于方程1,我们只需要取 x = 0 x=0 x=0 一个解。
- 对于方程2,暴力枚举验证的每次时间复杂度是 O ( p i log m ) O(p_i\log m) O(pilogm) 的。
- 但是时间复杂度为 O ( T ∑ i = 1 c p i log m ) O(T\sum_{i=1}^c p_i\log m) O(T∑i=1cpilogm),并不能通过本题。
算法二
- 现在的问题就是如何快速求出形如 x n ≡ 1 ( m o d p ) , p 是 质 数 x^n \equiv 1(mod~p),p是质数 xn≡1(mod p),p是质数
- 的方程在模 p p p 意义下的解的数量。
- 我们定义数论函数 f ( x ) = x n f(x)=x^n f(x)=xn。
- 显然 f ( x ) f(x) f(x) 是完全积性函数。
- 我们可以使用线性筛线性筛出,对于每个质数暴力快速幂计算。
- [ 1 , p ) [1,p) [1,p) 内质数个数是 O ( p ln p ) O(\frac{p}{\ln p}) O(lnpp) 的,然后对于每个质数 O ( log n ) O(\log n) O(logn) 计算,总的时间复杂度可以认为是 O ( p ) O(p) O(p),然后线性筛部分是 O ( p ) O(p) O(p) 的。
- 因此我们可以每次 O ( p ) O(p) O(p) 处理出来这个函数在 [ 0 , p ) [0,p) [0,p) 上的所有值对 p p p 取模的值,并 O ( p ) O(p) O(p) 求出解的数量,实现 O ( T ∑ i = 1 c p i ) O(T\sum_{i=1}^c p_i) O(T∑i=1cpi) 的时间复杂度。
#include <cmath>
#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
template <class T>
inline void read(T &x)
{
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
const int MaxN = 1e4 + 5;
int id, T, c, m, n;
inline int qpow(int a, int b, const int &mod)
{
int res = 1;
for (; b; b >>= 1, a = 1LL * a * a % mod)
if (b & 1)
res = 1LL * res * a % mod;
return res;
}
//the number of x (x^m=p)(x in [0,p))
inline int solve(const int &m, const int &p)
{
static bool sie[MaxN];
static int pri[MaxN], f[MaxN], cnt;
int res = 1;
memset(sie, 0, sizeof(sie));
cnt = 0;
f[1] = 1;
for (int i = 2; i < p; ++i)
{
if (!sie[i])
{
pri[++cnt] = i;
f[i] = qpow(i, m, p);
}
for (int j = 1; j <= m; ++j)
{
int x = i * pri[j];
if (x >= p) break;
sie[x] = true;
f[x] = 1LL * f[i] * f[pri[j]] % p;
if (i % pri[j] == 0)
break;
}
if (f[i] == 1)
++res;
}
return res;
}
int main()
{
freopen("division.in", "r", stdin);
freopen("division.out", "w", stdout);
#define mod 998244353
read(id), read(T);
while (T--)
{
read(c), read(m);
int ans = 1;
int x;
for (int i = 1; i <= c; ++i)
{
read(x);
ans = 1LL * ans * (solve(m - 1, x) + 1) % mod;
}
printf("%d\n", ans);
}
fclose(stdin);
fclose(stdout);
return 0;
}
算法三
- 实际上这道题有很优秀的做法。
- 此算法来自某个神仙考场上猜的结论。
- 方程 x n ≡ 1 ( m o d p ) , p 是 质 数 x^n \equiv 1(mod~p),p是质数 xn≡1(mod p),p是质数 在模 p p p 意义下的解的数量为 g c d ( n , p − 1 ) gcd(n,p-1) gcd(n,p−1)。
- 我们来证明一下这个显而易见的结论。
- 证明:
- x x x 的取值范围为 [ 1 , p ) [1,p) [1,p)。
- 这个方程形如一个 N N N 次剩余的形式。
- 我们令 g g g 表示 p p p 的一个原根。
- 根据原根的性质 ,集合 { x ∣ x = g k   m o d   p , k ∈ [ 1 , p ) , k ∈ Z } \{x|x=g^k\bmod p,k\in [1,p),k\in \mathbb Z\} {x∣x=gkmodp,k∈[1,p),k∈Z} 和集合 { 1 , 2 , … , p − 1 } \{1,2,\dots,p-1\} {1,2,…,p−1} 相等。
- 设 x = g y x=g^y x=gy( y ∈ [ 1 , p ) y\in[1,p) y∈[1,p))。
- 所以原方程可以表示为 g n × y ≡ g 0 ( m o d p ) g^{n\times y}\equiv g^0(mod~p) gn×y≡g0(mod p)。
- 根据 g p − 1 = 1 g^{p-1}=1 gp−1=1,我们得到 n × y ≡ 0 ( m o d ( p − 1 ) ) n\times y\equiv 0(mod~(p-1)) n×y≡0(mod (p−1))。
- 写成同余方程的形式即 ( p − 1 ) x + n y = 0 (p-1)x+ny=0 (p−1)x+ny=0。
- 显然我们可以取特解 x = y = 0 x=y=0 x=y=0,得到 y y y 的通解为 k ⋅ p − 1 ( p − 1 , n ) ( k ∈ Z ) k·\frac{p-1}{(p-1,n)}(k\in \mathbb Z) k⋅(p−1,n)p−1(k∈Z)。
- 我们代到 y y y 的取值范围中即有 1 ≤ k ⋅ p − 1 ( p − 1 , n ) ≤ p − 1 1\le k·\frac{p-1}{(p-1,n)}\le p-1 1≤k⋅(p−1,n)p−1≤p−1。
- 解出来 k k k 可以取 [ 1 , ( p − 1 , n ) ] [1,(p-1,n)] [1,(p−1,n)] 内的任意整数。
- 证毕。
- 那么对于每个质数就可以 O ( l o g p ) O(log~p) O(log p) 的时间复杂度求出 g c d gcd gcd 算答案了。
- 答案即为 ∏ i = 1 c ( g c d ( m − 1 , p − 1 ) + 1 ) \prod_{i=1}^c(gcd(m-1,p-1)+1) ∏i=1c(gcd(m−1,p−1)+1)。
- 可以轻松通过本题。
#include <cmath>
#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
const int mod = 998244353;
int id, T, c, m, n;
int main()
{
freopen("division.in", "r", stdin);
freopen("division.out", "w", stdout);
scanf("%d%d", &id, &T);
while (T--)
{
scanf("%d%d", &c, &m);
int ans = 1;
int x;
for (int i = 1; i <= c; ++i)
{
scanf("%d", &x);
ans = 1LL * ans * (1 + std::__gcd(m - 1, x - 1)) % mod;
}
std::cout << ans << std::endl;
}
fclose(stdin);
fclose(stdout);
return 0;
}