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

题意简述

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

  • 给定 (c) 个不超过 (t) 的质数 (p_i(i in [1, c])),有 (n = prod_limits{i = 1}^c p_i)

所有测试数据满足 (1 leq c leq 50,1 leq t leq 10^4,1 leq m leq 10^9),数据组数 (leq 10000)

题解

首先,由于已经给出了 (n) 的唯一分解式 (n = p_1p_2cdots p_c),故我们可以将 (n | x^m - x) 这个式子拆成由 (c) 个形如 (x^m equiv x ({ m mod} p_i)) 的同余方程组成的同余方程组。

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

所以,对于第 (i) 个方程,我们需要求 (s_i = sum_limits{x = 1}^p[x ^ m equiv x{ m mod }p])

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

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

[left(sum_{x = 1}^p[x ^ m equiv x{ m mod }p] ight) = { m gcd}(m - 1, p - 1) + 1 ]

证明如下:

我们要求满足 (x in [1, p])(x^m equiv x ({ m mod} p))(x) 的个数。

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

[g^{my} equiv g^y ({ m mod} p) ]

根据费马小定理:

[my equiv y ({ m mod} p - 1) Rightarrow (m - 1)y equiv 0 ({ m mod} p - 1) ]

(k = gcd(m - 1, p - 1)),两边同时除以 (k),得:

[frac{m - 1}{k}y equiv 0 ({ m mod} frac{p - 1}{k}) ]

由于此时 (gcd(frac{m - 1}{k}, frac{p - 1}{k}) = 1),因此 (y) 一定有 (frac{p - 1}{k} | y)。由于 (y in [0, p - 2]),显然,(frac{p - 1}{k}) 的任意小于 (k) 的非负整数倍((0 hicksim k - 1) 倍)均满足条件,因此 (y) 共有 (k) 种合法取值。

因此满足 (sum_{x = 1}^p[x ^ m equiv x{ m mod }p])(i) 共有 (k + 1) 个。

这样,本题的时间复杂度就从 (O(sum p_i)) 优化至了 (O(c imes log p_i))(log)(gcd) 的复杂度)。

代码

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

#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
", 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
", ans);
  }
  return 0;
}
原文地址:https://www.cnblogs.com/ImagineC/p/9880891.html