BZOJ3122 [Sdoi2013]随机数生成器 数论

本文介绍了一种利用离散对数解决特定递推数列问题的方法,并通过BSGS算法实现。针对给定的递推式X_{i}

题意:对于递推式X_{i}=(a*X_{i-1}+b)%p | X_{1}=c 求最小的n满足X_{n}=t 其中a,b,c,t,p为给定非负整数且p为质数,0<=a<=p-1,0<=b<=p-1,2<=p<=10^9

Sol:

把递推式展开得 X_{n}=x1*a^(n-1)+(a^(n-1)+a^(n-2)...a^0)*b

括号里是等比数列,根据求和公式得 X_{n}=x1*a^(n-1)+b*(1-a^(n-1))/(1-a)=a^(n-1)+b*(a^(n-1)-1)/(a-1)=t

左右同乘(a-1) -> 拆括号+合并同类项得a^(n-1)=(t*(a-1)+b)/(x1*(a-1)+b)

模意义下除法,用逆元搞

然后转化成已知a^(n-1)求n,离散对数问题,用BSGS做

最后注意下上边的式子中分母不能是0,特判a=1

此外还要特判t==x1 、a=0 、a=0&&b=1 等情况

Code:

#include<bits/stdc++.h>
#define debug(x) cout<<#x<<"="<<x<<endl
typedef long long ll;
using namespace std;

ll p,a,b,x1,tar;
int T;
map<ll,int> M;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

ll PowerMod(ll x,ll y,ll mod)
{
     x%=mod;ll res=1;
     while(y)
     {
             if(y&1) res=(res*x)%mod;
             x=(x*x)%mod;y>>=1;
     }return res;
}
int bsgs(ll x,ll y) // x ^ res = y
{
	M.clear();
	int mr=ceil(sqrt(p));
	ll num=y,base=PowerMod(x,mr,p);
	for(int i=0;i<=mr;i++)
	{
		M[num]=i;
		num=num*x%p;
	}
	num=base;
	for(int i=1;i<=mr;i++)
	{
		if(M.count(num)) return i*mr-M[num];
		num=num*base%p;
	}
	return -2;
}

int main()
{
	T=read();
	while(T--)
	{
		p=read();a=read();b=read();x1=read();tar=read();
		if(tar==x1){puts("1");continue;}
		if(a==0){printf("%d\n",(tar==b)?2:-1);continue;}
		if(a==1&&b==0){puts("-1");continue;}
		if(a==1) printf("%d\n",((tar-x1)*PowerMod(b,p-2,p)%p+p)%p+1);// x1+(n-1)*b=tar
		else // (a^(n-1))*x1+b*(a^(n-1)-1)/(a-1)=tar
		{
			ll num=(a-1+p)%p,inv=PowerMod((x1*num+b)%p,p-2,p);
			printf("%d\n",bsgs(a,(tar*num+b)%p*inv%p)+1);
		}
	}
	return 0;
}


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值