HDU 4992 Primitive Roots 数论-求原根

本文介绍了数论中的原根概念及其性质,并提供了如何找到原根的解题策略。通过素数预处理和性质应用,可以避免超时并找到所有原根。代码实现展示了如何根据性质3推导出模n的原根。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门:这题有毒,不要点!!

题意很简单:求所有原根,那么问题来了,何为原根?

设 (a,m)=1, 满足 ax1(mod m) 的最小的 x,称为a对m的阶,记为 
ordm(a) 
当 ordm(a)=ϕ(m) 时称为a为m的原根.

原根有什么性质能帮助解这道题?

1.nn=2,4,pe,2pe(p).

2.设 ϕ(m)=pr11pr22prkk,则gm的原根当且仅当对与所有的pi

gϕ(m)pi1(mod m)

3.ordm(ad)=ordm(a)(ordm(a),d)(利用这个性质可以求出所有原根)

好了,现在为了尽量不要超时,我们先预处理素数,所以写一个素数筛

然后,利用性质1,排除那些乍一看就不符合条件数(注:就算n符合性质1的形态,也不一定有原根)至于怎么处理嘛...

    if(n == 2)
    {
        printf("1\n");
        return;
    }
    if(n == 4)
    {
        printf("3\n");
        return;
    }
    if(!exist(n))//exist函数用来处理性质1的后两种形态
    {
        printf("-1\n");
        return;
    }
而以下是exist函数

bool exist(int n)
{
    if(n % 2 == 0)//无论是2p^n还是p^n,先统统化为p^n
        n /= 2;
    if(!vis[n]) return 1;//vis[i] = true说明i为合数,否则为质数,该判断的意思是,如果是质数,则一定满足p^n或2p*n形态
    for(int i = 3; i * i <= n; i += 2)
    {
        if(n % i == 0)
        {
            while(n % i == 0)
                n /= i;
            return n == 1;
        }
    }
    return 0;
}
说来,以上是我超时的主要位置,我刚开始并没有写bool函数,而是在main函数里面如下判断

if(n == 2)
        {
            printf("1\n");
            continue;
        }
        if(n == 4)
        {
            printf("3\n");
            continue;
        }
        int ncopy = n;
        if(n % 2 == 0)
            n /= 2;
        bool judge = true;
        if(vis[n])
        {
            for(int i = 3; i * i <= n; i+=2)
            {
                if(n % i == 0)
                {
                    while(n % i == 0) n /= i;
                    if(n == 1)
                    {
                        judge = true;
                        break;
                    }
                    else
                    {
                        judge = false;
                        break;
                    }

                }
            }
        }
        if(!judge)
        {
            printf("-1\n");
        }
        else
        {
            n = ncopy;
            solve(n);
        }

然后找不出哪里超时的我绝望地写了个愚蠢bool函数

bool exist(int n)
{
    if(n % 2 == 0)
        n /= 2;
    bool judge = true;
    if(vis[n])
    {
        for(int i = 3; i * i <= n; i += 2)
        {
            if(n % i == 0)
            {
                while(n % i == 0)
                    n /= i;
                if(n == 1)
                {
                    judge = true;
                    break;
                }
                else
                {
                    judge = false;
                    break;
                }
            }
        }
    }
    return judge;
}
这两种都超时了,至于为何,并不清楚,我只是明白,能写函数快速返回,绝对不要在主函数里面墨迹,而且,能return直接return,别玩花样...以后比赛时如果t了我就这么改,恩..

好了,接下来,我们利用性质2找到一个原根,然后利用性质3找到所有原根(性质3可以推导出,如果p是模n的原根,那么如果i与n的欧拉函数值互质,则p^i也是模n的原根)

此处有一点需要理解,模n的原根p的1~p-1次方模n的结果与1~n-1一一对应,也就是说,利用性质3找的原根模n就是原根结果

以下是完整代码

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<vector>
#include<set>
using namespace std;
typedef long long ll;
const int N = 1000000 + 5;
const int M = 1000 + 5;
vector<int>rec;
vector<int>ans;
bool vis[N];
void pre()//素数筛
{
    memset(vis, false, sizeof(vis));
    vis[0] = vis[1] = true;
    for(int i = 2; i <= 1000000; i++)
    {
        if(!vis[i])
        {
            for(ll j = (ll)i * (ll)i; j <= 1000000; j += (ll)i)
            {
                vis[j] = true;
            }
        }
    }
}
int euler(int n)//求欧拉函数值
{
    if(!vis[n]) return n - 1;
    int res = n, a = n;
    for(int i = 2; i * i <= a; i++)
    {
        if(a % i == 0)
        {
            res = res / i * (i - 1);
            while(a % i == 0) a /= i;
        }
    }
    if(a > 1) res = res / a * (a - 1);
    return res;
}
void ac(int n)//求数n的所有因数,一开始我求所有质因数,然后想像性质2一样用欧拉函数值一个一个除,进行判断,结果T了,比赛时如果遇到这种情况,我就改成求所有因数,然后直接判断
{
    rec.clear();
    if(!vis[n]) return;
    for(int i = 2; i*i <= n; i++)
    {
        if(n % i == 0)
        {
            rec.push_back(i);
            if(i * i != n)
                rec.push_back(n / i);
        }
    }
}
int power(int x, int a, int mod)//快速幂
{
    ll res = 1;
    ll tmp = x;
    while(a)
    {
        if(a & 1)
            res = res * tmp % mod;
        tmp = tmp * tmp % mod;
        a >>= 1;
    }
    return (int)res;

}
int gcd(int a, int b)//欧几里得求最大公约数
{
    if(a < b)
        swap(a, b);
    if(b == 0)
        return a;
    return gcd(b, a % b);
}
bool exist(int n)
{
    if(n % 2 == 0)
        n /= 2;
    if(!vis[n]) return 1;
    for(int i = 3; i * i <= n; i += 2)
    {
        if(n % i == 0)
        {
            while(n % i == 0)
                n /= i;
            return n == 1;
        }
    }
    return 0;
}
void solve(int n)
{
    if(n == 2)
    {
        printf("1\n");
        return;
    }
    if(n == 4)
    {
        printf("3\n");
        return;
    }
    if(!exist(n))
    {
        printf("-1\n");
        return;
    }
    ans.clear();
    int eu = euler(n);
    ac(eu);
    sort(rec.begin(), rec.end());
    int siz = rec.size();
    int mark = -1;
    for(int i = 2; i < n; i++)
    {
        if(power(i, eu, n) != 1) continue;
        bool ju = true;
        for(int j = 0; j < siz; j++)
        {
            if(power(i, rec[j], n) == 1)
            {
                ju = false;
                break;
            }
        }
        if(ju)
        {
            ans.push_back(i);
            mark = i;
            break;
        }
    }
    if(mark == -1)
    {
        printf("-1\n");
        return;
    }
    for(int i = 2; i < eu; i++)
    {
        if(gcd(eu, i) == 1)
        {
            int tmp = power(mark, i, n);
            ans.push_back(tmp);
        }
    }
    sort(ans.begin(), ans.end());
    int asize = ans.size();
    for(int i = 0; i < asize; i++)
    {
        if(i == 0)
        {
            printf("%d", ans[i]);
        }
        else
            printf(" %d", ans[i]);
    }
    printf("\n");
}
int main()
{
    pre();
    int n;
    while(~scanf("%d", &n))
    {
        solve(n);
    }
}
1000ms+过的,还是很慢的...




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值