1. 程式人生 > 實用技巧 >P3306-[SDOI2013]隨機數生成器【BSGS】

P3306-[SDOI2013]隨機數生成器【BSGS】

正題

題目連結:https://www.luogu.com.cn/problem/P3306


題目大意

給出一個\(p,a,b,x_1,t\),有\(x_i=ax_{i-1}+b\)
求一個最小的\(n\)使得\(x_n=t\)


解題思路

下標縮一下先變成\(x_0\)會更好算一點,只考慮\(x_0\)的貢獻就是\(x_0\times a^n\),這個比較好搞。

\(b\)的貢獻的話,對於第\(i\)次加入的\(b\)貢獻是\(a^{n-i}\)總共也就是\(b\times \sum_{i=0}^{n-1}a^i\)
通項公式一下合起來就是

\[x_0a^n+\frac{a^n-1}{a-1}b=t \]

\(a^n\)

提到前面來就是

\[a^n=\frac{t(a-1)+b}{xa-x+b} \]

後面那個是已知的,然後就是上\(\text{BSGS}\)就好了。

需要注意的是如果\(a=1\)就不能用通項公式了,得上\(\text{exgcd}\)來搞。

要特判的東西有點多就不多講了


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath>
#define ll long long
using namespace std;
ll T,p,a,b,x,t,ans;
map<ll,ll> v;
ll power(ll x,ll b){
    ll ans=1;
    while(b){
        if(b&1)ans=ans*x%p;
        x=x*x%p;b>>=1;
    }
    return ans;
}
ll exgcd(ll a,ll b,ll &x,ll &y){
	if(!b){
		x=1;y=0;
		return a;
	}
	ll d=exgcd(b,a%b,x,y);
	ll z=x;x=y;y=z-a/b*y;
	return d;
}
void works(ll a,ll b,ll p){
	ll x,y;
	ll d=exgcd(a,p,x,y);
	if(b%d){
		printf("-1\n");
		return;
	}
	x*=b/d;y*=b/d;
	printf("%lld\n",(x%(d*p)+d*p)%(d*p)+1);
}
ll work(ll a,ll b,ll p){
    if(!a&&!b)return 1;
    if(!a)return -2;
    ll t=sqrt(p)+1;v.clear();
    for(ll i=0,z=1;i<t;i++,z=z*a%p)
        v[z*b%p]=i;
    a=power(a,t);
    if(b==1||!a)return 1;
    else if(!a)return -2;
    ll ans=1e18;
    for(ll i=0,tmp=1;i<=t;i++,tmp=tmp*a%p){
        ll j=(v.find(tmp)!=v.end())?v[tmp]:-1;
        if(j>=0&&i*t-j>=0)ans=min(ans,i*t-j);
    }
    if(ans==1e18)return -2;
    return ans;
}
signed main()
{
    scanf("%lld",&T);
    while(T--){
        scanf("%lld%lld%lld%lld%lld",&p,&a,&b,&x,&t);
        if(!a&&!t&&b){puts("-1");continue;}
        if(x==t){puts("1");continue;}
        if(a==1){
            works(b,(t-x+p)%p,p);
            continue;
        }
        t=(t*(a-1)+b)%p;x=(x*a-x+b+p)%p;
        t=t*power(x,p-2)%p;t=(t+p)%p;
        printf("%lld\n",work(a,t,p)+1);
    }
    return 0;
}