省选算法学习-BSGS与exBSGS与二次剩余

前置知识

扩展欧几里得,快速幂

都是很基础的东西

扩展欧几里得

说实话这个东西我学了好几遍都没有懂,最近终于搞明白,可以考场现推了,故放到这里来加深印象

翡蜀定理

方程$ax+by=gcd(a,b)$一定有整数解

证明:

因为$gcd(a,b)=gcd(b,a$ $mod$ $b)$

所以假设我们已经求出来了$bx+(a$ $mod$ $b)y=gcd(b,a$ $mod$ $b)$的一组整数解$(p,q)$

因为$a$ $mod$ $b=a-(\lfloor \frac{a}{b} \rfloor \ast b)$

所以$bp+(a-a/b\ast b)q=ax+by$

$b(p-(a/b)q)+aq=ax+by$

所以$x=q,y=(p-(a/b)q)$是一组合法的解

所以我们可以递归$gcd$的过程中倒着算每一层的解

当$b=0$时的解为$x=1,y=0$

BSGS

问题提出

给定$a,b,p$,求最小的$x$,使得$a^x≡b(mod$ $p)$

问题求解

显然这个东西不能直接做

考虑分块的思想

定义$m=sqrt(p)$

设$x=i\ast m - j$

也就是$a^{i\ast m}≡a^j\ast b(mod$ $p)$

那么我们首先把$j=0...m-1$时的$a^j\ast b$插入一个哈希表

然后我们枚举$i$,在哈希表里面查询$a^{i\ast m}$有没有出现过,如果出现过,它最大的$j$是多少

然后就可以在$O(sqrt(p))$的时间内解决这个问题了

放个板子


namespace hash{
	ll first[1000010],next[1000010],val[1000010],hash[1000010],mod=926081,cnt=0;
	void init(){memset(first,0,sizeof(first));cnt=0;}
	void insert(ll w,ll pos){
		ll p=w%mod,u;
		for(u=first[p];u;u=next[u]){
			if(hash[u]==w){val[u]=pos;return;}
			if(!next[u]) break;
		}
		if(!next[u]){
			cnt++;
			if(!first[p]) first[p]=cnt;
			else next[u]=cnt;
			val[cnt]=pos;hash[cnt]=w;next[cnt]=0;
		}
	}
	ll find(ll w){
		ll p=w%mod,u;
		for(u=first[p];u;u=next[u]){
			if(hash[u]==w) return val[u];
		}
		return -1;
	}
}
ll qpow(ll a,ll b,ll p){
	ll re=1;
	while(b){
		if(b&1) re=re*a%p;
		a=a*a%p;b>>=1;
	}
	return re;
}
ll gcd(ll a,ll b){
	if(b==0) return a;
	return gcd(b,a%b);
}
ll bsgs(ll a,ll b,ll p){
	if(b==1) return 0;
	ll i,j,m=ceil(sqrt((double)p)),tmp=b,cur,base=qpow(a,m,p);
	hash::init();
	for(j=0;j<m;j++){
		hash::insert(tmp,j);
		tmp=tmp*a%p;
	}
	tmp=1;
	for(i=m;i<=p;i+=m){
		tmp=tmp*base%p;
                cur=hash::find(tmp);
		if(~cur) return i-cur;
	}
	return -1;
}

exBSGS

使用BSGS的时候要求$gcd(a,p)=1$,扩展版的exBSGS则不需要

具体操作是这样的:

除掉公约数

假设$tmp=gcd(a,p)$

那么$(y\ast tmp)^x=z\ast tmp(mod$ $q\ast tmp)$

其中$y,z,q$是新设出来的量,$y\ast tmp=a$,$z\ast tmp=b$,$q\ast tmp=p$

这一步可以看出,如果$b$不能整除$gcd(a,p)$,那么一定无解

转化

把等式两边的含$tmp$的东西提取出来,可以得到:

$y^{tmp}=z(mod$ $q)$

然后就可以继续递归下去处理了

代码


namespace hash{
	ll first[1000010],val[1000010],hash[1000010],next[1000010],cnt=0,mod=926081;
	void init(){memset(first,0,sizeof(first));cnt=0;}
	void insert(ll w,ll pos){
		ll p=w%mod,u;
		for(u=first[p];u;u=next[u]){
			if(hash[u]==w){val[u]=pos;return;}
			if(!next[u]) break;
		}
		if(!next[u]){
			cnt++;
			if(!first[p]) first[p]=cnt;
			else next[u]=cnt;
			next[cnt]=0;val[cnt]=pos;hash[cnt]=w;
		}
	}
	ll query(ll w){
		ll p=w%mod,u;
		for(u=first[p];u;u=next[u]){
			if(hash[u]==w) return val[u];
		}
		return -1;
	}
}
ll qpow(ll a,ll b,ll p){
	ll re=1;
	while(b){
		if(b&1) re=re*a%p;
		a=a*a%p;b>>=1;
	}
	return re;
}
ll gcd(ll a,ll b){
	if(b==0) return a;
	else return gcd(b,a%b);
}
ll bsgs(ll a,ll b,ll p){
	if(b==1) return 0;//不要忘了特判
	ll i,j,tmp=1,d=1,cnt=0;
	hash::init();
	while((tmp=gcd(a,p))!=1){
		if(b%tmp) return -1;
		cnt++;b/=tmp;p/=tmp;d=d*(a/tmp)%p;//注意这个d的写法
		if(b==d) return cnt;//记得写这个
	}
	ll m=ceil(sqrt(double(p))),base=qpow(a,m,p);//注意这两个东西一定要写在这里,不要写在while上面
	tmp=b;
	for(j=0;j<m;j++){
		hash::insert(tmp,j);
		tmp=(tmp*a)%p;
	}
	for(i=m;i<=p+m;i+=m){//这里注意p+m,不然的话可能会有少数情况挂掉
		d=(d*base)%p;//同时注意这里的tmp相当于是一开始就是上面的d而不是1,也就是一开始要乘上已经除掉的东西
		tmp=hash::query(d);
		if(tmp!=-1) return i-tmp+cnt;
	}
	return -1;
}

二次剩余(2019/4/14更新)

什么是二次剩余

考虑一个模意义下的方程:$x^2$ $mod$ $p=n$,其中$n\in [1,p-1]$,不考虑$n=0$时显然有解的情况

若这个方程的未知数$x$有在$[0,p-1]$中的整数解,我们称$n$为模$p$意义下的二次剩余,否则称为非二次剩余

判断是否为二次剩余

下文所有等式省略模意义

这里实际上有一个叫勒让德符号的东西,但是我觉得那玩意儿太麻烦了,就不说他的名字了,但是要知道是这个东西

以下方法仅仅对奇质数有效

由费马小定理,我们知道:$n^{p-1}=1$

那么显然$n^{\frac{p-1}{2}}=1$或者$-1$

那么若$x2=n$,对应上面两种情况,我们分别有$x=1$或者$-1$

显然这里再根据费马小定理,$x^{p-1}$ 只能是$1$

所以当$n^{\frac{p-1}{2}}=-1$时,$n$是非二次剩余,否则是二次剩余

在有二次剩余的情况下求解$x$

这个......我懒得写证明了,放a_crazy_czy菊苣的链接在这里

posted @ 2018-12-22 13:58  dedicatus545  阅读(273)  评论(0编辑  收藏  举报