TJOI 2017 (Pollard_rho,MR素数测试)

博客详细介绍了如何在TJOI 2017竞赛中使用Pollard_rho算法和Miller-Rabin素数测试解决求分数AB模M逆元的问题。通过分析质因子分解,简化求逆过程,避免TLE。最终代码使用了__int128数据类型来处理大数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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'} AB,其中 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'} AB,再判断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=a1a2amB=b1b2bm,所以可以通过依次求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'} AB使得 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=a1a2am,所以我们依次检查ai是否能被pj整除,若能整除,则一定有 a i = a i ′ ∗ p j e j a_i=a^{'}_i*p_j^{e_j} ai=aipjej,保留 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=a1a2an满足于M互质。
因为我们需要保证变化后的分数是等价的,所以每当A除以一个pj时,我们还需判断B是否也能除以一个pj。若不能,则说明 B A \frac{B}{A} AB化简后的 B ′ A ′ \frac{B'}{A'} AB中的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=b1b2bmbi=bipjej中的ej

可以用一个数组e,初始化e[i]=0:
对于 a i = a i ′ ∗ p j e j a_i=a^{'}_i*p_j^{e_j} ai=aipjej,e[j]-=ej
对于 b i = b i ′ ∗ p j e j b_i=b^{'}_i*p_j^{e_j} bi=bipjej,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=a1a2an, B ′ = b 1 ′ ⋅ b 2 ′ ⋯ b n ′ B'=b^{'}_1\cdot b^{'}_2\cdots b^{'}_n B=b1b2bn组成的 B ′ A ′ \frac{B'}{A'} AB并不等价于原来的分数,因为可能存在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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值