bzoj2242 : [SDOI2011]计算器 [BSGS算法]

第一次写一个比较高大上的数论题吧~~~虽然要花好长时间才明白一点,但是有收获吧~~对扩展欧几里得加深了理解。还认识了高大上的BSGS算法,哈哈~虽然还是一知半解,脑袋太小理解太慢,不易记住大哭

上别人的题解吧~~~


相关连接:

BSGS算法_Baby steps giant steps算法(无扩展)详解

BSGS[bzoj2242][bzoj3122]

关于第三问一些好的解析:

(1)baby step giant step,意为先小步后大步。由费马小定理可得答案不会超过p,我们可以把答案X看作k*m+i的形式,显然当m=sqrt(p)时复杂度最优。预处理m以内的hash值存入map(今天发现map真是个好东西), 枚举k,因为ni(y^k*m)*z%p=y^i%p,当当前值存在于哈希表是,则说明找到一个合法k与合法i,计入答案。

此时有一个特殊情况,若当前hash值为一,即ni(y^k*m)===z(mod p),则i=0;


(2)

第三问高次同余方程a^x=b(mod n),Baby Step Giant Step算法::

解高次同余方程a^x=b(mod n) n为质数
设x=i*m+j m=ceil(sqrt(n))
则a^im*a^j=b(mod n)
==>(a^m)^i*a^j=b(mod n)
求a^m模n的乘法逆元v=a^(n-m-1){
乘法逆元:若ax=1(mod n),则称a模n的乘法逆元是x
性质:K/x mod n = K*a mod n (将就着看,不是很科学)

证明v=a^(n-m-1)是乘法逆元:{
由费马小定理得a^(n-1)=1(mod n)
则a^m*a^(n-m-1)=1(mod n)
所以v=a^(n-m-1)
}
}
所以(a^m)^i*a^j=b(mod n)
==>a^j=b*v^i(mod n)
枚举j=0 ->m-1 将a^j mod n存入hash表
枚举i=0 ->m-1 每次计算 b*v^i mod n,若计算过程中发现b*v^i mod n在hash表中出现过,返回i*m+j
这样就解完了复杂度O(sqrt(n))当然这是你hash写得好才行,蒟蒻用map,或者二分判断,复杂度O(sqrt(n)logn)

复杂度O(sqrt(n)log(n))来自快速幂和hash

(3)

给定y、z、p,计算满足yx mod p=z的最小非负整数x。p为质数(没法写数学公式,以下内容用心去感受吧)

x = i*m + j.

y^(j)≡z∗y^(-i*m)) (mod p)

y^(j)≡z∗ine(y^(i*m)) (mod p)(逆元)

由费马小定理y^(p-1)≡1 (mod p) ine(y^m) = y^(p-m-1) 

ine(y^(i*m)≡ine(y^((i−1)m))∗y^(p-m-1) 

1.首先枚举同余符号左面,用一个hash保存(y^j,j),因为j可能等于0,所以hash[1]要赋为一个特殊值。

2.再枚举同余符号右面,如果hash(z∗ine(y^(i*m)))存在,就找到了一组解。

显然,m=sqrt(p)的时候复杂度最低为O(sqrt(p)),m=ceil(sqrt(p)).


革命尚未成功,同志仍需努力!

#include<cstdio>
#include<iostream>
#include <algorithm>
#include <map>
#include <cmath>
using namespace std;
typedef long long ll;
map<ll,ll>hash;
ll y,z,p;

ll power(ll a,ll b,ll mod){
	ll res=1;
	while(b){
		if(b&1) res=(res*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return res;
}
ll exgcd(ll a,ll b,ll &x,ll &y){
	ll res,t;
	if(b==0){
		x=1;y=0;
		return a;
	}
	res=exgcd(b,a%b,x,y);
	t=x;x=y;y=t-(a/b)*y;
	return res;
}
ll solve1(){
	cout<<power(y,z,p)<<endl;
}
ll solve2(){
	ll xx,yy,ans,d;
	d=exgcd(y,p,xx,yy);
	if(z%d!=0)
	cout<<"Orz, I cannot find x!"<<endl;
	else{
		ans=(z/d)*xx;
		ans=(ans%p+p)%p;   //求出的ans可能为负数,这里求出的是最小的非负解 
		cout<<ans<<endl;
	}
}
ll solve3(){
	y%=p;z%=p;
	if(!y&&!z)
	cout<<"1"<<endl;
	else if(!y){
		cout<<"Orz, I cannot find x!"<<endl;
	}
	else{
		ll m,v,e,res;
		m=ceil(sqrt(p));v=power(y,p-m-1,p);e=1;
		hash.clear();
		hash[1]=m+1;  //特殊情况
		
		for(ll i=1;i<=m;i++){
			e=e*y%p;
			if(!hash[e])
			hash[e]=i;
		} 
		res=-1;
		for(ll i=0;i<=m;i++){
			if(hash[z]){
				res=i*m+(hash[z]==m+1?0:hash[z]);
				break;
			}
			z=z*v%p;
		}
		if(res==-1)
		cout<<"Orz, I cannot find x!"<<endl;
		else
		cout<<res<<endl;
	}
}
int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
#endif
    int t,k;
    cin>>t>>k;
    for(int i=0;i<t;i++){
    	cin>>y>>z>>p;
    	//cout<<y<<z<<p;
    	if(k==1) solve1();
    	else if(k==2) solve2();
    	else solve3();
	}
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值