题意:
f(x) = x^x , (x的x次方)
令 g(x,y) = f(x) + f(y)
求 g(x,y) = 0 (mod p) 的数对(x,y)数量 ,其中 (x,y)为整数对,且1<=x<=y<p , p为质数,且p<1000 000,测试数据组数T = 100
解题思路:
f(x) + f(y) = 0 (%p) , 由于p为质数,根据费马小定理,x^(p-1) =1(%p), x^(x) * x^(p-1-x) = 1(%p) ,故 f(x) (%p) 不为0。 (1<=x<p)
对于f(x) + f(y) = 0 (%p) 可知 x!=y
记 num[x]为 次方%p为x的数的个数
答案为 ∑num[i]*num[n-i] (1<=i<p)
思路1:求出1..p-1的所有f(x)值,每次用快速幂计算,时间复杂度 O(T*n*logn) ,超时
思路2:根据线性筛素数的方法变形,计算出f(x)值:
如下:
f(1) = 1^1
f(2) = 2^2 f(4) = (2^2)*(2^2) * (2^4)
f(3) = 3^3 f(6) = (3^3)*(3^3) * (2^6) f(9) = (3^6)*(3^3) * (3^9)
f(4) = f(8) = (4^4)*(4^4) * (2^8) f(12) = (4^8)*(4^4) * (3^12)
f(5) = 5^5 f(10) = (5^5)*(5^5) * (2^10) f(15) = (5^10)*(5^5) * (3^15) f(25) = (5^15)*(5^10) * (5^25)
f(6) = f(12) = (6^6)*(6^6) * (2^12) f(18) = (6^12)*(6^6) * (3^18) f(30) = (6^18)*(6^12) * (5^30)
……
因此只需计算出质数的x次方,就可以计算出所有f(x)值,n以内素数个数大约为n/10,因此计算出素数次方总共需要 n/10 * logn,求出所有f(x)是线性的,因此总时间复杂度为O(T*n/10*logn),超时
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
typedef long long ll;
using namespace std;
int num[1010000];
int pri[1010000],P_num;
int pp[1010000],rpp[1010000],stamp[130];
bool use[1010000];
int powM(int a,int b,int m){
int ans=1;
while (b){
if (b&1) ans = (ll)ans*a%m;
a = (ll)a*a%m;
b>>=1;
}
return ans;
}
void getP(int P){
memset(use,false,sizeof(use));
P_num=0;
pp[1]=1;
pri[0]=0;
for (int i=2;i<P;i++)
{
if (!use[i]) {
pri[++P_num]=i;
pp[i] = powM(i,i,P);
}
int tmp=1,len=2;
stamp[1]=pp[i];
stamp[2]=(ll)pp[i]*pp[i]%P;
for (int j=1;j<=P_num&&i*pri[j]<P;j++){
use[i*pri[j]]=true;
if (len<120)
{
len+=2;
stamp[len] = (ll)stamp[len-2] * pp[i] *pp[i]%P;
}
tmp = (ll)tmp*stamp[pri[j]-pri[j-1]]%P;
if (i==pri[j])
rpp[j] = tmp;
else
rpp[j] = (ll)rpp[j]*pp[pri[j]]%P;
pp[i*pri[j]] = (ll) tmp * rpp[j]%P;
}
}
}
int main(){
int T;
scanf("%d",&T);
while (T-->0){
int n;
scanf("%d",&n);
memset(num,0,sizeof(num));
getP(n);
ll ans=0;
for (int i=1;i<n;i++){
num[pp[i]]++;
// cout<<i<<":"<<pp[i]<<endl;
ans+=num[n-pp[i]];
}
printf("%I64d\n",ans);
}
return 0;
}
思路3:考虑到整个计算是在模p剩余类群当中,求出关于p的一个原根r,则该群可由原根表示{r^1,r^2,r^3,.....,r^(p-1) },因此只需预处理出r的1到p-1次方,就可以表示出1..n-1的所有数,
例如:
p = 5,取原根 2
{1,2,3,4} 可以表示为{2^4, 2^1, 2^3, 2^2 }
然后根据欧拉降幂公式
1^1 = (2^4)^1 = 2^4 = 2^(4%4)
2^2 = (2^1)^2 = 2^2 = 2^(2%4)
3^3 = (2^3)^3 = 2^9 = 2^(9%4)
4^4 = (2^2)^4 = 2^8 = 2^(8%4)
时间复杂度O(n),通过
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
typedef long long ll;
using namespace std;
int num[1010000];
int pp[1010000];
// 求素数
int P_num,pri[1010000],pre[1010000];
bool use[1010000];
void getPrime(){
memset(use,0,sizeof(use));
memset(pre,0,sizeof(pre));
P_num = 0;
for (int i=2;i<1010000;i++){
if (!use[i]) pri[++P_num]=i, pre[i]=i;
for (int j=1;j<=P_num&&i*pri[j]<1010000;j++) {
use[i*pri[j]]=true;
if (i%pri[j]!=0)
pre[i*pri[j]]=pri[j];
else pre[i*pri[j]] = pre[i];
}
}
}
int powM(int a,int b,int M){
int ans=1;
while (b){
if (b&1) ans= (ll)ans*a%M;
a = (ll)a*a%M;
b>>=1;
}
return ans;
}
//求原根
int fac[30];
int getPrimitiveRoot(int P){
int cnt=0;
int x = P-1;
while (x>1){
fac[++cnt] = pre[x];
x /= pre[x];
}
int ans = 0;
for (int i=2;i<P;i++){
bool isProot=true;
for (int j=1;j<=cnt;j++)
if (powM(i,(P-1)/fac[j],P)==1){
isProot = false;
break;
}
if (isProot) {
ans = i;
break;
}
}
return ans;
}
int f2[1010000],powt[1010000];
void work(int P,int proot){
int t=1;
powt[0]=1;
for (int i=1;i<P;i++){
t = t*proot%P;
powt[i] = t;
f2[t] = i;
}
for (int i=1;i<P;i++)
pp[i] = powt[(ll)f2[i]*i%(P-1)];
}
int main(){
getPrime();
int T;
scanf("%d",&T);
while (T-->0){
int n;
scanf("%d",&n);
memset(num,0,sizeof(num));
work(n,getPrimitiveRoot(n));
ll ans=0;
for (int i=1;i<n;i++){
num[pp[i]]++;
ans+=num[n-pp[i]];
}
printf("%I64d\n",ans);
}
return 0;
}