HDU 4992 Primitive Roots 數論-求原根
阿新 • • 發佈:2019-01-22
題意很簡單:求所有原根,那麼問題來了,何為原根?
設 (a,m)=1, 滿足 ax≡1(modm) 的最小的 x,稱為a對m的階,記為
ordm(a)
當 ordm(a)=ϕ(m) 時稱為a為m的原根.
原根有什麼性質能幫助解這道題?
1.n有原根⇔n=2,4,pe,2pe(p為奇素數).
2.設 ϕ(m)=pr11pr22…prkk,則g是m的原根當且僅當對與所有的pi.
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函式,而是在main函式裡面如下判斷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; }
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+過的,還是很慢的...