bzoj-2219 数论之神

141 篇文章 0 订阅
12 篇文章 0 订阅

题意:

求方程X^A = B(mod 2*K + 1)

X ∈[0, 2K] 内的解的个数;


题解:
一道数论的好题;

涉及知识点大概有:Crt推论,BSGS,EXGCD,原根与指标;

这道题的主要问题在于两点:

第一点:取模数不是质数,无法利用通常的方式解方程;

但是有中国剩余定理这个东西,定理的推论告诉我们:

一个取模数互质的同余方程组(未必线性),组合起来之后,这个同余方程解的个数为各方程解的个数的乘积

(组合起来的方程的取模数为所有数的积;实际上这里解的范围都是属于[0 ,自己取模数) )

这点十分重要呢,它不仅证明了解的求法,而且如果有任意一个方程无解,那么整个就都是无解的;

解决了这一点之后,就是第二点:如何处理一个方程的解的个数;

暂且令当前方程为x^A =B (mod p^d);

因为中国剩余定理要求取模数互质,所以将2*K+1分解质因子作为所有方程;

然而我只会处理p^1的情况怎么办啊= =;

分类讨论:

p^d|B :这时p^d是B的因子,那么实际上就是x^A =0 (mod p^d)了;

如果设x=p^k*q,这里(p,q)=1;

k其实表示的是x中p因子的个数,那么在x^A中的个数就是k*A,也就有k*A>=d;

k>=d/A,也就是说k最小为ceil(d/A)  (ceil为上取整函数);

因为x的取值范围是[0,p^d),而x=p^k*q ,显然d>k;

所以解的个数就是p^d/p^k=p^(d-k);


gcd(p^d,B)=p^k:这时二者之间有一个公约数;

直观的想,如果等式两边同时除一个p^k就解决啦;

但是要保证A|k,要不然的话x^A中约数p的个数就不可能和B的相等(显然);

当我们成功的把方程p^k*x'^A= B (mod p^d)

化简为 x'^A= B/(p^k) (mod p^(d-k))之后

我们就挂啦!

因为这样是不对的,确切的说是x'的取值范围变化了;

那么变化了多少呢?原来的范围是[0,p^(d-k/A)),而后变成了[0,p^(d-k));

原因就在于mod值的缩小,但是对于答案的影响和上面一样:

答案是第二个方程答案的p^(k-k/A)倍;


第二个方程怎么解? 取指标啊!

取完指标就是线性同余方程了,方程个数就是gcd(A,φ(p^d));

但是有个坑,同余方程可能无解啊。。

所以还要大步~小步~BSGS求出一个 以p^d的原根为底,关于B的,对p^d取模的 指标lnB (好TM绕);

然后判断gcd(A,φ(p^d))| lnB,如果不是因子则无解!

那么这题就结束了,可喜可贺;


什么gcd(B,p^d)=1没讨论?其实不就是上一段怎么解的事吗;

什么复杂度?我可是BZ这题Rank1 (倒数)的人啊;

【反正我咋看咋O(n);

反正我是线性筛搞了个素数表= =

然并卵,似乎复杂度没有本质改变?


代码:


#include<math.h>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 140142
using namespace std;
typedef long long ll;
const ll INF=0x7f7f7f7f7f7f7f7f7fll;
struct Hash_Set
{
	ll head[N],next[N],X[N],val[N],tot;
	void clear()
	{
		tot=0;
		memset(head,0,sizeof(head));
		memset(next,0,sizeof(next));
		memset(val,-1,sizeof(val));
		memset(X,0,sizeof(X));
	}
	ll& operator [](ll x)
	{
		ll index=x%N;
		for(ll i=head[index];i;i=next[i])
		{
			if(X[i]==x)
				return val[i];
		}
		next[++tot]=head[index];
		head[index]=tot;
		X[tot]=x;
		return val[tot];
	}
}hash;
ll pri[N],top,a[N],b[N];
bool vis[N];
void init()
{
	for(ll i=2;i<N;i++)
	{
		if(!vis[i])
			pri[++top]=i;
		for(ll j=1;j<=top&&i*pri[j]<N;j++)
		{
			vis[i*pri[j]]=1;
			if(i%pri[j]==0)
				break;
		}
	}
}
ll pow(ll x,ll y,ll mod)
{
	ll ret=1;
	while(y)
	{
		if(y&1)
			ret=ret*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ret;
}
ll gcd(ll a,ll b)
{
	ll t=a%b;
	while(t)
	{
		a=b,b=t;
		t=a%b;
	}
	return b;
}
void exgcd(ll a,ll b,ll &x,ll &y,ll &d)
{
	if(!b)
		x=1,y=0,d=a;
	else
	{
		exgcd(b,a%b,y,x,d);
		y-=a/b*x;
	}
}
ll inv(ll X,ll mod)
{
	ll x,y,d;
	exgcd(X,mod,x,y,d);
	return (x%mod+mod)%mod;
}
ll Root(ll x,ll phi)
{
	ll st[N],top=0,temp=phi;
	for(ll i=1,p=2;p*p<=temp;i++,p=pri[i])
	{
		if(temp%p==0)
		{
			st[++top]=phi/p;
			while(temp%p==0)
				temp/=p;
		}
	}
	if(temp!=1)
		st[++top]=phi/temp;
	for(ll i=1,j;i<=phi;i++)
	{
		for(j=1;j<=top;j++)
		{
			if(pow(i,st[j],x)==1)
				break;
		}
		if(j>top)
			return i;
	}
}
ll BSGS(ll A,ll B,ll C)
{
	hash.clear();
	ll bk=ceil(sqrt(C*1.0)),i,k,D,temp;
	for(i=0,D=1;i<bk;i++,D=D*A%C)
	{
		if(hash[D]==-1)
			hash[D]=i;
	}
	temp=inv(D,C);
	for(i=0,k=B;i<=bk;i++,k=k*temp%C)
	{
		if(hash[k]!=-1)
			return i*bk+hash[k];
	}
	return 0;
}
ll slove(ll A,ll B,ll C)
{
	ll i,j,k,p,mod,lnB,g,d,cnt,ans;
	for(i=1,p=2,ans=1;p*p<=C;i++,p=pri[i])
	{
		if(C%p==0)
		{
			mod=1,cnt=0;
			while(C%p==0)
				C/=p,mod*=p,cnt++;
			if(B%mod==0)
			{
				d=pow(p,cnt-ceil(cnt*1.0/A),INF);
			}
			else
			{
				k=0;
				while(B%p==0)
					k++,B/=p;
				if(k%A)	d=0;
				else
				{
					g=Root(mod,mod-mod/p);
					lnB=BSGS(g,B,mod);
					d=gcd(A,mod-mod/p);
					if(lnB%d)	d=0;
					d*=pow(p,k-k/A,INF);
				}
			}
			ans*=d;
		}
	}
	if(C!=1)
	{
			mod=C,p=C,cnt=1;
			if(B%mod==0)
			{
				d=pow(p,cnt-ceil(cnt*1.0/A),INF);
			}
			else
			{
				k=0;
				while(B%p==0)
					k++,B/=p;
				if(k%A)	d=0;
				else
				{
					g=Root(mod,mod-mod/p);
					lnB=BSGS(g,B,mod);
					d=gcd(A,mod-mod/p);
					if(lnB%d)	d=0;
					d*=pow(p,k-k/A,INF);
				}
			}
			ans*=d;
	}
	return ans;
}
int main()
{
	init();
	ll c,T,A,B,C;
	scanf("%lld",&T);
	for(c=1;c<=T;c++)
	{
		scanf("%lld%lld%lld",&A,&B,&C);
		printf("%lld\n",slove(A,B,C<<1|1));
	}
	return 0;
}



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值