Codeforces Round #305 (Div. 2) C. Mike and Frog(思维题-扩展欧几里得 特判)

题目

给一个模数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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值