题目
给一个模数m(2<=m<=1e6)
开始有数1,记为h1,
还有数2,记为h2(0<=h1,h2<m)
每一天,数i,都会变成(xi*hi+yi)%m
其中x1,x2,y1,y2是四个给出的系数,也满足在[0,m)之间
现在有两个目标,a1和a2,也都在[0,m)之间
想要某一天,h1变成a1,h2变成a2,问最少的天数是多少
题目保证初始h1不等于a1,h2不等于a2
思路来源
乱搞AC
题解
首先找到h1第一次变成a1,h2第一次变成a2的天数,分别记为cnt1和cnt2,
①由于数只有m个,如果m次之内到不了的话说明无解,
再找到第二次变成a1,第二次变成a2的天数,减去第一次的天数即记为循环节cyc1和cyc2,
②同上,如果2*m次内到不了第二次,说明没有循环节
③如果2*m天内有一天合法,输出答案
④有这样一种情况,m=16,对1进行*2和+0操作,
1->2->4->8->0->0->0,目标是2,则仅经过一次,
特判这种不存在循环节的情况
⑤否则cnt1,cnt2,cyc1,cyc2存在,
问题等价于求解cnt1+cyc1*k1=cnt2+cyc2*k2(k1>=0,k2>=0)的最小等式值
不妨cnt1<cnt2,则cyc1*k1-cyc2*k2=cnt2-cnt1
根据扩欧,如果右边不是gcd的倍数,无解
否则设g=gcd(cyc1,cyc2),dif=cnt2-cnt1
记mn1=cyc1/g,mn2=cyc2/g,now=dif/g
先解mn1*k1+mn2*(-k2)=1的式子,解出(k1,-k2)后,对(-k2)*=now
即得mn1*k1+mn2*(-k2)=now的一组合法(k1,-k2),
注意到使式子最小,应该-k2是<=0的最大解,
即先取模使之>=0的最小,若不为0再减去mn1
解出后对-k2取负即得k2,答案是cnt2+mn2*g*k2
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
typedef long long ll;
ll m,h[3],a[3],x[3],y[3];
ll cnt[3],cyc[3];
ll nxt(ll now,ll a,ll b){
return (1ll*a*now+b)%m;
}
void extgcd(ll a,ll b,ll &x,ll &y){
ll d=a;
if(b)extgcd(b,a%b,y,x),y-=(a/b)*x;
else x=1,y=0;
}
ll cal(){
cnt[1]=cnt[2]=2*m+5;
for(int i=1;i<=2*m;++i){
for(int j=1;j<=2;++j){
h[j]=nxt(h[j],x[j],y[j]);
if(h[j]==a[j]){
if(cnt[j]>i)cnt[j]=i;
else if(!cyc[j])cyc[j]=i-cnt[j];
}
}
if(h[1]==a[1] && h[2]==a[2]){
return i;
}
}
if(cnt[1]>m || cnt[2]>m)return -1;
if(!cyc[1] || !cyc[2])return -1; //1->2->4->8->0->0 (%16) 则2只出现一次
if(cnt[2]<cnt[1]){
swap(cnt[1],cnt[2]);
swap(cyc[1],cyc[2]);
}
ll dif=cnt[2]-cnt[1];
ll g=__gcd(cyc[1],cyc[2]);
if(dif%g)return -1;
dif/=g;cyc[1]/=g;cyc[2]/=g;
ll x,y;
extgcd(cyc[1],cyc[2],x,y);
y*=dif;
y=(y%cyc[1]+cyc[1])%cyc[1];
if(y)y-=cyc[1];//y <=0的最大非正数
y=-y;
return cnt[2]+cyc[2]*g*y;
}
int main(){
scanf("%lld",&m);
scanf("%lld%lld",&h[1],&a[1]);
scanf("%lld%lld",&x[1],&y[1]);
scanf("%lld%lld",&h[2],&a[2]);
scanf("%lld%lld",&x[2],&y[2]);
//printf("x1:%lld k1:%lld x2:%lld k2:%lld\n",cnt[1],cyc[1],cnt[2],cyc[2]);
printf("%lld\n",cal());
return 0;
}