模擬賽Day1(20200203) T2 數論題【前綴最小逆元】

題目描述:

在這裏插入圖片描述
在這裏插入圖片描述

題目分析:

逆元可以看做一個僞隨機序列,前p\sqrt p個數的逆元的最小值期望可以縮小到O(p)O(\sqrt p)級別。

所以當p109p\le10^9時可以枚舉前p\sqrt p個數,求出最小逆元,再枚舉逆元求出對應的位置,時間複雜度期望O(p)O(\sqrt p)
具體實現時也可以從小到大枚舉ii,記錄逆元的最小值mnmn,如果inv[i]<mninv[i]<mn則計入答案,當inv[i]<iinv[i]<i時就可以退出了(將此時的所有答案翻轉過來也是答案,它是對稱的,比如答案有2  (p+1)/22 ~~(p+1)/2,那麼必然有(p+1)/2  2(p+1)/2 ~~2

由於多組數據,當pp過大的時候直接枚舉會超時,觀察能夠形成前綴最小值的if(i)=kp+1i*f(i)=kp+1,因爲當ii達到p\sqrt pf(i)f(i)期望已經<p<\sqrt p了,所以kk不會很大。

我們可以枚舉kk,然後將kp+1kp+1用Pollard_Rho算法分解因數求出所有的if(i)i*f(i),最後將其按ii排序,檢驗f(i)f(i)是否是前綴最小,輸出即可。時間複雜度爲O(kk((kp+1)0.25+σ(kp+1)))O(\sum_{k}k*((kp+1)^{0.25}+\sigma(kp+1)))
具體實現時取k=40k=40即可通過(我最慢的一個點跑了994ms)。
也有不設具體的kk值的方法,假設已經枚舉到了kk,已經分解出的因數爲i1,i2...,imi_1,i_2...,i_m,如果(ix1)inv[ix1]kp+1(i_x-1)*\text{inv}[i_{x-1}]\le kp+1,說明在ix1i_{x-1}ixi_x之間不可能還有解,如果對於所有的xx這個不等式都成立(以及imi_m(p+1)/2(p+1)/2),那麼說明當前枚舉到的kk已經足夠了。直接設k=40多好啊

Code:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
int T;
namespace solve1{
	const int N = 3000005;
	int inv[N],q[N],cnt;
	void main(LL p){
		cnt=0,inv[0]=inv[1]=1;int mn=p;
		for(int i=2;;i++){
			inv[i]=(p-p/i)*inv[p%i]%p;
			if(inv[i]<i) break;
			if(inv[i]<mn) mn=inv[i],q[++cnt]=i;
		}
		printf("%d\n",cnt?cnt*2-(q[cnt]==inv[q[cnt]]):0);
		for(int i=1;i<=cnt;i++) printf("%d %d\n",q[i],inv[q[i]]);
		for(int i=cnt-(q[cnt]==inv[q[cnt]]);i>=1;i--) printf("%d %d\n",inv[q[i]],q[i]);
	}
}
namespace solve2{
	LL pr[65],base[6]={2,3,7,61,19260817};int pc;
	inline LL mul(LL a,LL b,LL p){LL r=a*b-(LL)((long double)a/p*b+0.5)*p;return r<0?r+p:r;}
	inline LL Pow(LL a,LL b,LL p){
		LL s=1; for(;b;b>>=1,a=mul(a,a,p)) if(b&1) s=mul(s,a,p);
		return s;
	}
	bool Miller_Rabin(LL n){
		for(int i=0;i<5;i++) if(n==base[i]) return 1; else if(n%base[i]==0) return 0;
		LL d=n-1;int k=0;
		while(!(d&1)) d>>=1,k++;
		for(int i=0;i<5;i++){
			LL x=Pow(base[i],d,n),y;
			for(int i=1;i<=k&&x!=1;i++,x=y)
				if((y=mul(x,x,n))==1&&x!=n-1) return 0;
			if(x!=1) return 0;
		}
		return 1;
	}
	LL gcd(LL a,LL b){return b?gcd(b,a%b):a;}
	LL Rho(LL n,int c){
		LL x=rand()%n+1,y=x,d=1,q=1;
		for(int k=1;;k<<=1,y=x,q=1){
			for(int i=1;i<=k&&d==1;i++){
				q=mul(abs((x=(mul(x,x,n)+c)%n)-y),q,n);
				if(!(i&127)) d=gcd(q,n);
			}
			if(d>1||(d=gcd(q,n))>1) return d==n?Rho(n,c+1):d;
		}
	}
	void Pollard(LL n){
		while(!(n&1)) n>>=1,pr[++pc]=2;
		if(n==1) return;
		if(Miller_Rabin(n)) {pr[++pc]=n;return;}
		LL p=Rho(n,3);
		Pollard(p),Pollard(n/p);
	}
	pair<LL,LL>ans[3000005]; int tot,cnt;
	LL d[60005];int dc;
	void main(LL p){
		tot=cnt=0;
		for(int k=1;k<=40;k++){
			LL t=k*p+1,pw;
			pc=0,Pollard(t),sort(pr+1,pr+pc+1);
			d[dc=1]=1,pr[pc+1]=0;
			for(int i=1,j,e;i<=pc;i=j){
				for(j=i+1,e=1;pr[i]==pr[j];j++,e++);
				for(int j=dc,k;j>=1;j--)
					for(k=1,pw=pr[i];k<=e;k++,pw*=pr[i]) d[++dc]=d[j]*pw;
			}
			for(int i=2;i<=dc;i++) if(d[i]<p&&t/d[i]<p) ans[++tot]=(make_pair(d[i],t/d[i]));
		}
		sort(ans+1,ans+1+tot); LL mn=p;
		for(int i=1;i<=tot;i++)
			if(ans[i].second<mn) mn=ans[i].second,ans[++cnt]=ans[i];
		printf("%d\n",cnt);
		for(int i=1;i<=cnt;i++) printf("%lld %lld\n",ans[i].first,ans[i].second);
	}
}
int main()
{
	scanf("%d",&T);LL p;
	while(T--){
		scanf("%lld",&p);
		if(p<=1e9) solve1::main(p);
		else solve2::main(p);
	}
}
發佈了379 篇原創文章 · 獲贊 139 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章