• 浅谈阶与原根


    阶的定义:若a,p均为正整数,且gcd(a,p)=1,称满足a^{r}\equiv 1(mod p)的最小正整数r为a模p的阶,记作ord_{p}a.

    定义枯燥乏味,举个栗子,如a=3,p=7,根据暴力递推我们可以得到这样一张有向有环图

     注意:这不是特例,再举个栗子,如a=2,p=7,可以得到

     其中节点的编号即为出现的模数

    根据欧拉定理可知,当gcd(a,p)=1,a>0,p>0,p为素数时一定有a^{\phi (p)}\equiv 1(mod p),再根据阶的定义,一定有阶r | \phi (p),这说明在这张图上走\phi (p)步一定会回到原点,但没有保证的是这\phi (p)步一定是把这个环走了一遍,也可能走了两遍...n遍,所以这个环的长度一定是\phi (p)的因子,那求阶可能想到的一种做法就是去枚举\phi (p)的所有因子,看最小的a的幂次模p等于1是什么时候,但这并不是最好的做法。

    假设阶是d,a^{x}\equiv 1(mod p),那么d | x,又知道欧拉定理是a^{\phi (p)}\equiv 1(mod p), 根据唯一分解定理分解\phi (p)=p_{1}^{e_{1}}\cdot p{_{2}}^{e_{2}}\cdot \cdot \cdot p_{n}^{e_{n}},先令d=\phi (p),枚举\phi (p)的每个质因子p_{i},当d%p_{i}==0并且ad/pi" role="presentation">ad/pi时,d/=p_{i},否则什么都不做,这说明的是此时的p_{i}对于d来说相当于对着环走了p_{i}次,最后虽然也是回到了原点,但并不是环的长度,所以要除掉,如果d/p_{i}后取模不是1了说明回不到原点,因此这个因子是关键的,不能除掉。综上所述,假设环的长度为L,除掉的相当于n*L中的n的因子,这里力求的是L的长度,所以要使得n为1,因此枚举所有的\phi (p)的因子。

    还有一个前置小芝士:\phi (p)为1到p-1中与p互质的正整数的个数,显而易见素数的欧拉函数的值为p-1

    求阶代码:

    1. #include
    2. using namespace std;
    3. using LL = long long;
    4. int main() {
    5. ios::sync_with_stdio(false);
    6. cin.tie(nullptr);
    7. int a, p;//底数和模数
    8. cin >> a >> p;
    9. function qp = [&](LL a, LL b) {
    10. LL res = 1;
    11. for (; b; b >>= 1, a = a * a % p) {
    12. if (b & 1) {
    13. res = res * a % p;
    14. }
    15. }
    16. return res % p;
    17. };
    18. int d = p - 1, m = p - 1;
    19. vector<int> z;
    20. for (int i = 2; i * i <= m; i++) {
    21. if (m % i == 0) {
    22. z.push_back(i);
    23. while (m % i == 0) {
    24. m /= i;
    25. }
    26. }
    27. }
    28. if (m != 1) {//如果还剩下一个没取到的素数还要把它放进因子集合里
    29. z.push_back(m);
    30. }
    31. int len = z.size();
    32. for (int i = 0; i < len; i++) {
    33. while (d % z[i] == 0 and qp(a, d / z[i]) == 1) {
    34. d /= z[i];
    35. }
    36. }
    37. cout << d << '\n';//阶
    38. return 0;
    39. }

    原根

    原根的定义:如果g(mod m)的阶为\phi (m),那么g就是m的原根。

    原根就是特殊情况的阶。

    g^{0},g^{1},...,g^{\phi (m)-1}构成了模m的简化剩余系。

    只有1,2,4,p^{a},2p^{a}存在原根(p为奇素数 (奇素数:除2以外的素数) )。

    注意:2的幂次可能是没有原根的

    结论1:原根很多

    结论2:最小的原根不会很大且2到p-1之间随机分布

    如何判断一个数g是否是原根?

    类比于求阶的计算,设模数m,枚举\phi (m)的所有因子p_{i},如果所有g^{\phi (m)/p_{i}}\not\equiv 1(mod m),那么g就是原根,否则不是原根,即阶可以变得更小,所以阶不等于\phi (m)

    注意:如果a是p的原根,a不一定是p^{2}的原根,如果a是p^{2}的原根,那么一定是更高次的原根

    如果g是m的原根,那么循环节里一定会出现1到m之间与m互质的数,都可以表示成g^{i}其中(i>=0,i<\phi (m)) (简化剩余系)

    根据上述的结论2可知,求解原根的时候可以从1开始暴力往上计算。

    求解原根代码:

    1. #include
    2. using namespace std;
    3. using LL = long long;
    4. int main() {
    5. ios::sync_with_stdio(false);
    6. cin.tie(nullptr);
    7. int p;//求解素数p的原根
    8. cin >> p;
    9. function qp = [&](LL a, LL b) {
    10. LL res = 1;
    11. for (; b; b >>= 1, a = a * a % p) {
    12. if (b & 1) {
    13. res = res * a % p;
    14. }
    15. }
    16. return res % p;
    17. };
    18. vector<int> z;//因子集合
    19. int m = p - 1;
    20. for (int i = 2; i * i <= m; i++) {
    21. if (m % i == 0) {
    22. z.push_back(i);
    23. while (m % i == 0) {
    24. m /= i;
    25. }
    26. }
    27. }
    28. if (m != 1) {//还有素数没有除完
    29. z.push_back(m);
    30. }
    31. int len = z.size();
    32. for (int g = 1; g < p; g++) {
    33. bool ok = true;
    34. for (int i = 0; i < len; i++) {
    35. if (qp(g, (p - 1) / z[i]) == 1) {//阶可以更小,因此不是原根
    36. ok = false;
    37. break;
    38. }
    39. }
    40. if (ok) {
    41. cout << g << '\n';
    42. exit(0);
    43. }
    44. }
    45. //否则没有原根
    46. return 0;
    47. }

  • 相关阅读:
    2022年Q2加密市场投融资报告:GameFi成为投资关键词
    PowerMax——业界首个引入NVIDIA BlueField DPU的高端存储
    【阅读和学习代码】VoxelNet
    go基础10 -字符串的高效构造与转换
    Git版本管理
    Springboot+网上眼镜商场 毕业设计-附源码241659
    swagger2文档基于knife4j 2.0.5二次封装工具
    【贝叶斯分类2】朴素贝叶斯分类器
    性能测试 —— Jmeter分布式测试的注意事项和常见问题
    ConcurrentHashMap--addCount()
  • 原文地址:https://blog.csdn.net/eyuhaobanga/article/details/127583642