1. 程式人生 > >HDU 4992 Primitive Roots 數論-求原根

HDU 4992 Primitive Roots 數論-求原根

題意很簡單:求所有原根,那麼問題來了,何為原根?

設 (a,m)=1, 滿足 ax1(modm) 的最小的 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(modm)

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+過的,還是很慢的...