题解 [ABC212G] Power Pair

没想到 ABC 居然会考原根!数论没学好的我根本不会,赛时还看着形式像离散对数,以为是 BSGS 的什么妙用呢。

给定质数 \(p\),求在 \([0, p-1]\) 范围内的 \((x, y)\) 满足存在 \(n\) 使 \(x^n \equiv y \pmod p\) 的有序数对 \((x, y)\) 数量。

根本不是离散对数!这题的前置知识是原根。

什么是原根

对于质数 \(p\),若最小的满足 \(a^x \equiv 1 \pmod p\)\(x = p-1\) ,则称 \(a\) 是模 \(p\) 意义下的原根。(事实上原根的定义是满足 \(x = \varphi(p)\),不过 \(p\) 是质数时 \(\varphi(p) = p-1\)

首先来考虑暴力怎么做。暴力得枚举每一个数,再枚举其幂在模 \(p\) 意义下的循环,时间复杂度大概是 \(n^2\) 的(打表发现循环在 \(\sqrt n\) 级别,所以说不定复杂度是 \(n \sqrt n\))。

时间复杂度高的原因主要是这个幂在模意义下没有什么规律,如果能转换成倍数,那么就可以直接除以它们的 \(\gcd\) 算出循环节了。

由原根的定义可得,若 \(a\) 是模 \(p\) 意义下的原根,那么 \(a^x \bmod p\)\(x \in [1, p-1]\) 时是两两不同的,所以设 \(r\) 是模 \(p\) 意义下的原根,则一定存在唯一 \(r^a = x, r^b = y\)(因为 \(x=0\)\(y\) 只能为 \(0\),所以这里先不考虑 \(x = y = 0\) 的情况,等后面加上)

那么问题转化成 \(r^{an} \equiv r^b \pmod p \Rightarrow an \equiv b \pmod {p-1}\),注意这里不是直接把幂放下来,而是因为原根那个幂两两不同的性质可以保证这样转化的正确性。
这样就把原来的幂转化成倍数了,可以愉快地使用 \(\gcd\) 的式子了。

问题转化成求

\[\sum_{a=1}^{p-1} \frac{p-1}{\gcd(a, p-1)} \]

遗憾的是,还不行,这样需要 \(O(p)\) 的复杂度,太慢了,我们需要加速这个运算。
注意到 \(\gcd\) 的结果必须是 \(p-1\) 的约数,也就是根号级别的,所以我们只要算出来对于每一个约数出现多少次就可以了。
\(f(g)\) 表示和 \(p-1\)\(\gcd\)\(g\)\([1, p-1]\) 中数的个数。那么问题就变成了 \(\sum \frac{p-1}{g} \times f(g)\)

对于计算 \(f(g)\),可以使用欧拉函数。\(\gcd\) 要是 \(g\),那么乘上的部分就必须和 \(\frac{p-1}{g}\) 互质,即 \(\varphi(\frac{p-1}{g})\),用欧拉函数的公式 \(\varphi(n) = n \prod \frac{p_i-1}{p_i}\) 就可以了。

最后答案还要加上 \(x = y = 0\) 的时候。

代码
#include <iostream>
#define int long long
const int P = 998244353;
int p, ans;
int phi(int x) {
    int an = x;
    for (int i = 2; i*i <= x; i++) {
        if (x % i) continue;
        while (x % i == 0) x /= i;
        an = an / i * (i-1);
    }
    if (x > 1) an = an / x * (x-1);
    return an;
}
int get(int d) { return (phi(p/d) % P) * ((p/d) % P)  % P; }
signed main() {
    std::cin >> p; p --;
    for (int i = 1; i*i <= p; i++) {
        if (p % i) continue;
        ans = (ans + get(i)) % P;
        if (i * i != p) ans = (ans + get(p/i)) % P; 
    }
    std::cout << (ans + 1) % P;
}
posted @ 2021-08-01 09:08  Acfboy  阅读(202)  评论(0)    收藏  举报