[51nod 1223] x^A mod B问题 - bsgs,原根,中国剩余定理,二进制分组


X^A mod B = C。给出A B C,求<= B的所有X。
例如:B = 11,A = 3,B = 5。
3^3 Mod 11 = 5
所有数据中, 解的数量不超过Sqrt(B)

特别像bzoj的那一道“数论之神”(准确的说应该是加强版),首先我们考虑当B是素数时我们怎么做。

显然我们只要求出一个原根g,然后求出C的指标,就能将问题转化为AindX≡indC(mod B-1)的问题 在问题保证了解的数量不超过sqrt(B)的情况下 可以O(B^0.5)求解。

然后我们会发现上面的做法是可以推广的,只要在mod B意义下存在原根(B为1,2,4,p,2p或p^k,p为奇素数,这里只考虑p^k的情况),我们就可以闷声大讨论,得到所有的解了。

具体来说分为三类:(B=p^k)

1. gcd(C,B)=1 可以直接用上面的方法

2. C≡0(mod B) 这时只要令x中p的项为s次,满足As>=k即可,也很好求解

3. C≡p^a*d 此时显然存在A|a 这时显然我们可以求出A'=A B'=p^(k-a) C'=d时的所有x 最后满足条件的x就是这样的x+mp^(k-a+a/A)

然后我们发现我们还会做B为奇数时候的情况 我们可以对B质因数分解 对于每个pi^ki分别做一次 根据中国剩余定理,每一组这样的解都对应了一组最终的解 且最终的解也一定满足在mod pi^ki时也满足条件 这样我们求出每一个部分的解 CRT合并即可。

可是问题来了 当2^k|B时 我们应该如何解决这样的问题呢?

下午问了好多人 感觉大家都不是太会 找了标程才发现原来有一种基于解的个数的做法。

考虑x^A ≡ C(mod 2^k)

那么显然也有x^A ≡ C(mod 2^(k-1))

假设我们已经求出了x^A ≡ C(mod 2^(k-1))的所有解x

就可以对每个x和x+2^(k-1)暴力判定是否满足要求 就相当于对x进行了一次二进制分组

当解的个数为S时,这样的时间复杂度为O(kSlogB)的

为什么这样做的复杂度是正确的 而不会造成某一层可行解过多 而下一层却都不可行呢?

我们考虑 x^A ≡ C (mod 2^k) 和 x^A ≡ C (mod 2^(k-1)) 的解数

我们带入 x和 x+2^(k-1) 发现有对于

x^A ≡ C 和 (x+2^(k-1))^A ≡ C 的判定

而 (x+2^(k-1))^A ≡ x^A + x^(A-1)2^(k-1) ≡ x^A + (x mod 2)2^(k-1)

因此当x为奇数时 x和x+2^(k-1) 中有且仅有一解

而当x为偶数 通过大量打表验证(其实是我不会证)我们发现是正确的!

所以就O(跑得过)???


#include"bits/stdc++.h"

using namespace std;

const int N=40005,hmod=1000003,INF=0x7fffffff;

typedef pair<int,int> pii;
typedef long long LL;

struct HashMap{
	int h[hmod+5],nxt[N],val[N],flag[N],ndc,flag_NULL;
	HashMap(){ndc=flag_NULL=0;}
	int&find(int x){
		for(int i=h[x%hmod];i;i=nxt[i])
			if(flag[i]==x)return val[i];
		return flag_NULL;
	}
	int&operator[](int x){
		int&q=find(x);
		if(q)return q;
		nxt[++ndc]=h[x%hmod],h[x%hmod]=ndc,flag[ndc]=x;
		return val[ndc];
	}
	void clear(){
		while(ndc){
			h[flag[ndc]%hmod]=0;
			val[ndc]=nxt[ndc]=flag[ndc]=0;
			ndc--;
		}
	}
}F;

int gcd(int x,int y){return y?gcd(y,x%y):x;}

int power(int a,int t,int P){
	int r=1;
	while(t){
		if(t&1)r=(LL)r*a%P;
		a=(LL)a*a%P;t>>=1;
	}
	return r;
}

int bsgs(int g,int mod,int r,int lim){
	F.clear();
	int S=sqrt(lim),l=1,m=1;
	for(int i=0;i<S;i++)F[l]=i,l=(long long)l*g%mod;
	l=power(l,lim-1,mod);
	for(int i=0;i*S<=lim;i++){
		if((long long)r*m%mod==1)return i*S;
		int x=F.find((long long)r*m%mod);
		if(x)return i*S+x;
		m=(long long)m*l%mod;
	}
	return -1;
}

bool chc(int g,int l,int mo){
	int r=l;
	for(int i=2;i*i<=r;i++)if(r%i==0){
		while(r%i==0)r/=i;
		if(power(g,l/i,mo)==1)return false;
	}
	if(r>1)return power(g,l/r,mo)!=1;
	return true;
}

void exgcd(int a,int b,LL&x,LL&y){
	if(b==0){x=1,y=0;return;}
	exgcd(b,a%b,y,x);y-=a/b*x;
}

int T,a,P,r,x[10][N],y[N],tmp[N],bs[10],ti[10],l;

void solve(int k,int a,int r,int p,int t){
	if(p==2){
		y[++y[0]]=r&1;
		for(int i=1;i<=t;i++){
			int mo=1<<i,dl=mo>>1;
			tmp[0]=0;
			for(int j=1;j<=y[0];j++){
				if(power(y[j],a,mo)==r%mo)tmp[++tmp[0]]=y[j];
				if(power(y[j]+dl,a,mo)==r%mo)tmp[++tmp[0]]=y[j]+dl;
			}
			memcpy(y,tmp,tmp[0]+1<<2);
		}
		memcpy(x[k],y,y[0]+1<<2);
		return;
	}
	int mo=power(p,t,INF);
	r%=mo;
	y[0]=0;
	if(r==0){
		int b=power(p,(t+a-1)/a,INF);
		x[k][0]=mo/b;
		for(int i=1;i<=x[k][0];i++)x[k][i]=b*(i-1);
		return;
	}
	if(r%p==0){
		int _r=r,qt=0,_m,_k;
		while(_r%p==0)++qt,_r/=p;
		if(qt%a){x[k][0]=0;return;}
		solve(k,a,_r,p,t-qt);
		y[0]=0;
		_m=power(p,t-qt+qt/a,INF),_k=r/_m;
		for(int i=1;i<=x[k][0];i++)for(int j=0;j<_k;j++)y[++y[0]]=j*_m+x[k][i];
		memcpy(x[k],y,y[0]+1<<2);
		return;
	}
	int g,ri=mo/p*(p-1);
	if(chc(2,ri,mo))g=2;else for(g=3;!chc(g,ri,mo);++g);
	int ind=bsgs(g,mo,r,ri),_a=a;
	a=gcd(ri,a);
	if(ind%a){x[k][0]=0;return;}
	LL _x,_y;
	exgcd(_a,ri,_x,_y);
	_x=(_x%ri*ind/a%ri+ri)%ri;
	x[k][0]=a;
	int fi=power(g,_x,mo),mi=power(g,ri/a,mo);
	for(int i=1;i<=a;i++)x[k][i]=fi,fi=(LL)fi*mi%mo;
}

int ans[N];

int CRT(int r1,int m1,int r2,int m2){
	LL x,y,z=m1*m2;exgcd(m1,m2,x,y);
	return ((x%z*(r2-r1)%z*m1+r1)%z+z)%z;
}

void dfs(int p,int r,int mo){
	if(p==l+1){ans[++ans[0]]=r;return;}
	int _mo=power(bs[p],ti[p],INF);
	for(int i=1;i<=x[p][0];i++)dfs(p+1,CRT(r,mo,x[p][i],_mo),mo*_mo);
}

void work(){
	int _P=P;
	l=0;
	if(P==1){puts("0");return;} 
	for(int i=2;i*i<=_P;i++)if(_P%i==0){
		int t=0;
		while(_P%i==0)_P/=i,++t;
		bs[++l]=i,ti[l]=t;
		solve(l,a,r,i,t);
		if(x[l][0]==0){puts("No Solution");return;}
	}
	if(_P>1){
		bs[++l]=_P,ti[l]=1,solve(l,a,r,_P,1);
		if(x[l][0]==0){puts("No Solution");return;}
	}
	ans[0]=0;
	if(l==1)memcpy(ans,x[1],x[1][0]+1<<2); 
	else for(int i=1,m=power(bs[1],ti[1],INF);i<=x[1][0];i++)dfs(2,x[1][i],m);
	int n=ans[0];
	sort(ans+1,ans+n+1);
	for(int i=1;i<=n;i++)printf("%d%c",ans[i]," \n"[i==n]);
}

int main(){
	scanf("%d",&T);
	while(T--)scanf("%d%d%d",&a,&P,&r),work();
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值