TJOI 2017 (Pollard_rho,MR素数测试)
题意是求分数 B A \frac{B}{A} AB在模M下的逆元,没有则输出-1。
本题的两大核心是:Pollard_rho算法,和MR素数测试算法。
B A \frac{B}{A} AB在模M下有逆元当且仅当 g c d ( A , M ) = 1 gcd(A,M)=1 gcd(A,M)=1,因为分数不是最简的,所以只要分数可以化简到 B ′ A ′ \frac{B'}{A'} A′B′,其中 g c d ( A ′ , M ) = 1 gcd(A',M)=1 gcd(A′,M)=1即有逆元。
我一开始的思路是先将 B A \frac{B}{A} AB化到最简分数 B ′ A ′ \frac{B'}{A'} A′B′,再判断A’是否有逆元。由于B’和A‘超出了264的范围,所以不能直接求 g c d gcd gcd以及约分。因为 A = a 1 ⋅ a 2 ⋯ a m , B = b 1 ⋅ b 2 ⋯ b m A=a_1\cdot a_2\cdots a_m,B=b_1\cdot b_2\cdots b_m A=a1⋅a2⋯am,B=b1⋅b2⋯bm,所以可以通过依次求ai,bi的质因子分解式,将A,B的质因子分解式求出,在通过A,B的质因子分解式去化简 B A \frac{B}{A} AB,再求M的质因子分解式,这样就可以判断是否 g c d ( A ′ , M ) = 1 gcd(A',M)=1 gcd(A′,M)=1了。
思路是简单明了的,答案能也保证正确,但是TLE。
在参考了别人的代码后,我发现并不用求A,B的质因子分解式,甚至也不用求M的质因子分解式,只需要求M的质因子集合即可。其实并不需要将 B A \frac{B}{A} AB化到最简,只需要将 B A \frac{B}{A} AB化简到 B ′ A ′ \frac{B'}{A'} A′B′使得 g c d ( A ′ , M ) = 1 gcd(A',M)=1 gcd(A′,M)=1即可。
在得到M的质因子集合
s
=
{
p
1
,
p
2
,
⋯
p
n
}
s=\{p_1,p_2,\cdots p_n\}
s={p1,p2,⋯pn}之后,想要让
g
c
d
(
A
′
,
M
)
=
1
gcd(A',M)=1
gcd(A′,M)=1,那么A’必须不能包含集合s里面的质因子。因为
A
=
a
1
⋅
a
2
⋯
a
m
A=a_1\cdot a_2\cdots a_m
A=a1⋅a2⋯am,所以我们依次检查ai是否能被pj整除,若能整除,则一定有
a
i
=
a
i
′
∗
p
j
e
j
a_i=a^{'}_i*p_j^{e_j}
ai=ai′∗pjej,保留
a
′
a^{'}
a′并统计
e
j
e^j
ej。得到的
A
′
=
a
1
′
⋅
a
2
′
⋯
a
n
′
A'=a^{'}_1\cdot a^{'}_2\cdots a^{'}_n
A′=a1′⋅a2′⋯an′满足于M互质。
因为我们需要保证变化后的分数是等价的,所以每当A除以一个pj时,我们还需判断B是否也能除以一个pj。若不能,则说明
B
A
\frac{B}{A}
AB化简后的
B
′
A
′
\frac{B'}{A'}
A′B′中的A’中必含有集合s里面至少一个素因子。所以我们还需统计
B
=
b
1
⋅
b
2
⋯
b
m
,
b
i
=
b
i
′
∗
p
j
e
j
B=b_1\cdot b_2\cdots b_m,b_i=b^{'}_i*p_j^{e_j}
B=b1⋅b2⋯bm,bi=bi′∗pjej中的ej。
可以用一个数组e,初始化e[i]=0:
对于
a
i
=
a
i
′
∗
p
j
e
j
a_i=a^{'}_i*p_j^{e_j}
ai=ai′∗pjej,e[j]-=ej。
对于
b
i
=
b
i
′
∗
p
j
e
j
b_i=b^{'}_i*p_j^{e_j}
bi=bi′∗pjej,e[j]+=ej。
若存在e[j]<0,则说明
B
A
\frac{B}{A}
AB并不存在逆元。
若对于所有e[j]>=0,则一定存在逆元。
注意,最后得到的 A ′ = a 1 ′ ⋅ a 2 ′ ⋯ a n ′ A'=a^{'}_1\cdot a^{'}_2\cdots a^{'}_n A′=a1′⋅a2′⋯an′, B ′ = b 1 ′ ⋅ b 2 ′ ⋯ b n ′ B'=b^{'}_1\cdot b^{'}_2\cdots b^{'}_n B′=b1′⋅b2′⋯bn′组成的 B ′ A ′ \frac{B'}{A'} A′B′并不等价于原来的分数,因为可能存在e[i]>0,说明了B在约去了A中包含的质因子pi之外还多约了 p i e [ i ] p_i^{{e[i]}} pie[i]。所以需将多约去的返还给B。
(不明白题目保证1<M,ai,bi<2*10^8,但是64bits的long long 过不了?)
所以本题实现使用了__int128。
代码如下:
#include<iostream>
#include<cstdio>
#include<utility>
#include<unordered_map>
#include<vector>
#include<algorithm>
#include<cstdlib>
#include<ctime>
using namespace std;
typedef long long ll;
const int max_n=1e4+5;
ll b[max_n];
ll a[25][max_n];
ll c[55];
ll M[55];
ll aa[max_n],bb[max_n];
ll n,m,k;
vector<ll> f;
ll gcd(ll a,ll b)
{
ll t;
while(b)
{
t=a;
a=b;
b=t%b;
}
return a;
}
ll qpow(ll a,ll n,ll mod)
{
ll ans=1;
while(n)
{
if(n&1)ans=(__int128)ans*a%mod;
a=(__int128)a*a%mod;
n>>=1;
}
return ans;
}
bool mr_test(ll n)
{
if(n<2)return false;
else if(n==2)return true;
else if(n%2==0)return false;
ll a=n-1,b=0;
while(a%2==0)a/=2,b++;
for(int i=0;i<10;i++)
{
ll x=rand()%(n-2)+2;
ll y=qpow(x,a,n);
for(int j=0;j<b;j++)
{
x=(__int128)y*y%n;
if(x==1&&y!=1&&y!=n-1)return false;
y=x;
}
if(y!=1)return false;
}
return true;
}
void pollard_rho(ll n)
{
if(n==1)return ;
if(mr_test(n)){
f.push_back(n);
return ;
}
ll c=rand()%(n-1)+1;
ll s=0,t=0;
ll v=1;
ll goal,step=1;
for(goal=1;;goal<<=1,s=t,v=1)
{
for(;step<=goal;step++)
{
t=((__int128)t*t%n+c)%n;
v=(__int128)v*abs(t-s)%n;
if(step%127==0)
{
ll d=gcd(v,n);
if(d>1){
while(n%d==0)n/=d;
pollard_rho(n);
pollard_rho(d);
return ;
}
}
}
ll d=gcd(v,n);
if(d>1){
while(n%d==0)n/=d;
pollard_rho(n);
pollard_rho(d);
return ;
}
}
}
void solve(void)
{
for(int i=1;i<=k;i++,f.clear())
{
pollard_rho(M[i]);
sort(f.begin(),f.end());
int cnt=unique(f.begin(),f.end())-f.begin();
vector<ll> e(cnt);
for(int j=1;j<=m;j++)aa[j]=a[c[i]][j],bb[j]=b[j];
bool flag=false;
for(int j=0;j<cnt;j++)
{
for(int l=1;l<=m;l++)while(bb[l]%f[j]==0)bb[l]/=f[j],e[j]++;
for(int l=1;l<=m;l++)while(aa[l]%f[j]==0)aa[l]/=f[j],e[j]--;
if(e[j]<0){
flag=true;
break;
}
}
if(flag){
printf("-1\n");
continue;
}
ll ansa=1,ansb=1,phi=M[i];
for(int j=0;j<cnt;j++)phi-=phi/f[j];
for(int j=1;j<=m;j++)ansa=(__int128)ansa*aa[j]%M[i];
for(int j=1;j<=m;j++)ansb=(__int128)ansb*bb[j]%M[i];
for(int j=0;j<cnt;j++)ansb=(__int128)ansb*qpow(f[j],e[j],M[i])%M[i];//把除多的补回来
ll rev=qpow(ansa,phi-1,M[i]);
ll ans=(__int128)ansb*rev%M[i];
printf("%lld\n",ans);
}
return ;
}
int main(void)
{
srand(time(NULL));
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1;i<=m;i++)
scanf("%lld",&b[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%lld",&a[i][j]);
for(int i=1;i<=k;i++)
scanf("%lld %lld",&c[i],&M[i]);
solve();
}