题目大意:有$n$ 个小于质数$p$ 的非负整数$a_i$ ,你想知道有多少对$i,j(1 \leqslant i<j\leqslant n)$ ,使得模$p$ 意义下$\frac{1}{a_i+a_j} \equiv \frac{1}{a_i}+\frac{1}{a_j}$ ,即这两数的和的逆元等于它们逆元的和,注意零元没有逆元。
二次剩余
$$
(a+b)^{-1} \equiv a^{-1}+b^{-1}(mod , p)\\
1 \equiv (a+b)(a^{-1}+b^{-1})(mod , p)\\
a^{-1}b+ab^{-1}+1 \equiv 0(mod , p)\\
a^2+b^2+ab\equiv 0 (mod , p)\\
(ab^{-1})^2+(ab^{-1})+1\equiv 0 (mod , p)\\
ab^{-1}\equiv \frac{-1\pm\sqrt{-3}}{2}(mod , p)
$$
故原式等价于模$p$ 意义下,两数之比为$\frac{-1\pm\sqrt{-3}}{2}$ ,根号可以用二次剩余解决。
下面分类考虑几种情况:
- $p=2$ ,此时可以直接求得$-3$ 的二次剩余是$1$
- $(\frac{-3}{p})=1$ ,套用上述公式遍历一遍数组可求得
- $(\frac{-3}{p})=-1$ ,即$-3$ 在模$p$ 下不存在二次剩余,故不存在满足条件的对
- $(\frac{-3}{p})=0$ ,即$p|-3$ ,此时$\sqrt{-3} \equiv 0 (mod , p)$
需要注意的是,此题会出现long long相乘溢出的情况,可以用快速乘处理。
代码如下:
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <map>
using namespace std;
typedef long long ll;
const int N=100005;
ll T,n,p,w,a[N];
map<ll,ll>mp;
ll Min(ll a,ll b){
return a<b?a:b;
}
ll Mul(ll a,ll b){
ll r=0;
while(b){
if(b&1)r=(r+a)%p;
a=(a+a)%p;
b>>=1;
}
return r;
}
struct Complex{
ll x,y;
Complex friend operator*(Complex x,Complex y){
Complex z;
z.x=(Mul(x.x,y.x)+Mul(Mul(x.y,y.y),w))%p;
z.y=(Mul(x.y,y.x)+Mul(x.x,y.y))%p;
return z;
}
};
ll powmod(ll a,ll n){
ll r=1;
while(n){
if(n&1)r=Mul(r,a);
a=Mul(a,a);
n>>=1;
}
return r;
}
ll ComPow(Complex a,ll n){
Complex r;
r.x=1;r.y=0;
while(n){
if(n&1)r=r*a;
a=a*a;
n>>=1;
}
return Min(r.x%p,(p-r.x%p)%p);
}
ll Cipolla(ll a,ll p){
ll x=rand()%p;
while(powmod((Mul(x,x)-a+p)%p,(p-1)/2)!=p-1)
x=rand()%p;
w=(Mul(x,x)-a+p)%p;
Complex t;
t.x=x;t.y=1;
return ComPow(t,(p+1)/2);
}
ll solve(ll rd,ll p){
mp.clear();
ll ans=0;
ll x=Mul(((-1+rd)%p+p)%p,powmod(2,p-2));
ll y=Mul(((-1-rd)%p+p)%p,powmod(2,p-2));
if(x==y){
for(int i=n-1;i>=0;--i)if(a[i]%p){
ll b=Mul(a[i],x);
if(mp.count(b))ans+=mp[b];
mp[a[i]%p]++;
}
}else{
for(int i=n-1;i>=0;--i)if(a[i]%p){
ll bx=Mul(a[i],x),by=Mul(a[i],y);
if(mp.count(bx))ans+=mp[bx];
if(mp.count(by))ans+=mp[by];
mp[a[i]%p]++;
}
}
return ans;
}
int main(void){
cin>>T;
while(T--){
cin>>n>>p;
for(int i=0;i<n;++i)
cin>>a[i];
ll ans;
if(p==2){
ans=solve(1,p);
}else if(powmod(p-3,(p-1)/2)==1){
ll rd=Cipolla(p-3,p);
ans=solve(rd,p);
}else if(powmod(p-3,(p-1)/2)==0){
ans=solve(0,p);
}else ans=0;
cout<<ans<<"\n";
}
}
立方公式
除了用二次剩余还有另一种更优雅的解法。
$$
(a+b)^{-1} \equiv a^{-1}+b^{-1}(mod , p)\\
1 \equiv (a+b)(a^{-1}+b^{-1})(mod , p)\\
a^{-1}b+ab^{-1}+1 \equiv 0(mod , p)\\
a^2+b^2+ab\equiv 0 (mod , p)\\
$$
当$a-b \not \equiv 0 (mod , p)$ 时,可以在上述等式两边同乘上$a-b$ ,得到
$$
(a-b)(a^2+ab+b^2)\equiv 0 (mod ,p)\\
a^3-b^3\equiv0(mod , p)
$$
如此只需记录当$a \not\equiv b(mod ,p)$ 时,所有满足$a^3-b^3\equiv 0 (mod ,p)$ 的对数即可。
查看代码 。