bzoj3122 线性同余方程

本文探讨了数列求解问题,通过引入BSGS算法解决特定类型的数论问题,具体阐述了如何推导通项公式并利用该算法找到最小的n值,同时讨论了解决过程中遇到的细节问题及优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://www.lydsy.com/JudgeOnline/problem.php?id=3122


看了一眼,以为是脑残题,可以先找出一个循环,然后再搞搞,发现找出循环代价太大,或者说有可能有些数多次出现了,但并没有循环。


然后推公式吧:(卧槽,就几个月没碰数列就tm不会做了~   然后搞出一个通项)

xn= a^(n-1) x1 +b*  (a^(n-1)-1)/(a-1)                答案就是求使得xn=t mod p中n最小的值。

然后exgcd解方程,把最小的a^(n-1)解x(其实是在mod p的情况下)出来了,然后怎么办?       我以为一定是x能够整除以a,然后YY了一个类似于背包二进制优化的方法来做,直到对排出错误才发现不对。

暴力枚举?不可行! 暴力枚举复杂度又变回去,相当于解方程还浪费了时间。

然后没办法,到处搜题解,直到看到一个叫BSGS的东东。


简单介绍一下BSGS:

就是解a^x = b mod p  已知a,b,p求解x的算法。

参考:http://www.cnblogs.com/yuiffy/p/3877381.html

参考2(详细):http://blog.youkuaiyun.com/clove_unique/article/details/50740412

然后貌似这样能够找到最小的x.(我也不是很懂QAQ,貌似因为hash的时候存的是最小的j,同时i也是从最小的开始枚举,这样貌似x就是最小的了~~~~)

然后还有一个坑点:


我hash[1]=0对吧,但是枚举i计算b*a^(-m*i)的时候就发现找不到了(明明出现过),肿么办?  网上有人利用了hash.count看这个位置是否被赋过值.

也有hash[1]=m+1(hash值一定小于m的,因为最多枚举到m-1,然后找到了再变成0)。

这两种都可行,但貌似用count的更快- -(不懂QAQ)。


最后,orz各种10组数据跑进250ms的超级神犇&&超级大神,orz各种跑进1s的大神,orzYY出各种解法的大神。

蒟蒻贴码:

(PS:跑得很慢- - ||||     跑了2s度,被甩了10倍)

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<cmath>
#include<iostream>
#include<map> 
#define ll long long
using namespace std;
/*
真是弱爆了,连数列都不会做了。
推到: xn= a^(n-1) x1 +b*  (a^(n-1)-1)/(a-1)
c=ni(a-1)
t=a^(n-1)(bc+x1)-bc  mod p
令 x=a^(n-1) A=bc+x1 t+bc=B 
B=Ax mod p 
*/
ll p,a,b,x1,t;
ll A,B;
map<ll,int >hash;
ll qk(ll x,ll y)
{
	ll res=1;
	while(y)
	{
		if(y&1)res=(res*(x%p))%p;
		y>>=1;
		x=(x*x)%p;
	}
	return res;
}
ll exgcd(ll a,ll b,ll &x,ll &y)//ax+by=gcd(a,b)
{
	if(b==0)
	{
		x=1;
		y=0;
		return a;
	}
	ll d=exgcd(b,a%b,x,y);
	// 
	ll t=x;
	x=y;
	y=t-a/b*y;
	return d;
}
void work(ll x)//BSGS
{
	//求解 a^n = x mod p
	//
	hash.clear();
	a%=p;
	ll m=(ll)(sqrt(p)+0.5);
	//变为a^j = x* a^(-m*i)mod p 
	ll t=1;
	hash[1]=0;
	for(int j=1;j<m;j++)
	{
		t=t*a%p;
		if(!hash.count(t))hash[t]=j;		
	}
	ll I=qk(qk(a,m),p-2);
	ll IS=1;
	for(int k=0;k<m;k++)
	{
		
		if(hash.count(IS*x%p))
		{
			int i=hash[IS*x%p];
			printf("%lld\n",k*m+i+1);
			return ;
		}
		IS=IS*I%p;
	}
	printf("-1\n");
}
void work2()
{
	A=b%p;
	B=(t-x1+p)%p;
	//Ax+py=B
	ll x,y;
	ll d=exgcd(A,p,x,y);
	if(B%d!=0)printf("-1\n");
	else
	{
		x*=B/d;
		x%=p;
		if(x<0)x+=p;
		printf("%lld\n",x+1);
	}
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld%lld%lld%lld%lld",&p,&a,&b,&x1,&t);
		if(x1==t)
		{
			printf("1\n");
			continue;
		}
		if(a==0)
		{
			if(t==b)printf("2\n");
			else printf("-1\n");
			continue;
		}
		if(a==1)
		{
			work2();
			continue;
		}
		ll c=qk(a-1,p-2);
		A=(b*c+x1)%p;
		B=(t+b*c)%p;
		//求解Ax=B mod p   AX+py=B 最小的x 
		ll x,y;
		ll d=exgcd(A,p,x,y);
		if(B%d!=0)printf("-1\n");
		else
		{
			ll k=B/d;
			x*=k;
			y*=k;
			ll u=p/d;
			x=(x%u+u)%u;
			//x=a^n-1
			work(x);
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值