CodeForces 548C - Mike and Frog(思维+暴力)
题目链接 https://cn.vjudge.net/problem/CodeForces-548C
【题意】
给定9个正整数 m, h1, a1, x1, y1, h2, a2, x2, y2 有两种植物,其中h1和h2分别是它们的初始高度,每经过一个单位时间,它们的高度就会变成h’=(x*h+y)%m, 两种植物都从时刻0开始计时,现在问你是否存在这样一个时刻t,使得在时刻t,第一株植物的高度为a1同时第二株植物的高度恰好是a2,输出最小的t,如果t不存在,输出-1(0<=所有变量<=1e6,h1!=a1,h2!=a2)
【思路】
一开始就直接按照题意暴力做的,循环1e6次,如果还没法到达目标状态就输出-1,然而WA了,然后就不太会做了,看网上的题解好像都是卡住2*m的范围求循环节,为什么是2*m,我也没太想清楚。然后我就用了下面的这另一种方法做了,
首先要注意的是,因为有mod m,所以肯定会有循环出现,但是这不是一个简单的“环”,或者说不是所有能变换到的数字都在循环里,有些只会出现一次就再也不会出现了,举个最简单的例子来说,如果m=10,x=2,y=0,然后h从1开始,经过不断的计算,我们可以得到一系列h的值为1,2,4,8,6(16mod10),2(12mod10),好了2重复出现过,这时已经找到了循环节,但是注意一开始的那个1在以后的变换中再也不会出现了。
在解决这道题的时候,代码里用fir变量记录目标高度第一次出现的时间,然后len记录循环节长度,都初始化为-1,这个len比较特殊,如果目标高度不在环里,那么len就还是-1表示目标高度只会出现一次。
如果说这两种植物的目标高度有一个压根就没出现过,那肯定无解;
否则的话,我们还要去判断这两个目标高度是处在“环”中呢,还是说出现一次就再也不会出现了,如果两个目标高度都不在“环”中,都只出现一次的话,那么如果它们出现的时间fir相同,说明fir就是唯一的解,否则无解;
如果一个在“环”中,另一个只出现一次,那么有解的条件就必须是fir[只出现一次]>=fir[在环中] 同时 fir[只出现一次]=fir[在环中]+k*len[环] k是任意非负整数,即只出现一次的fir时间要晚于在“环”中的fir,同时它们的时间差还正好是循环节的整数倍才行
如果都在“环”中的话,那满足要求时一定有fir[0]+k1×len[0]=fir[1]+k2×len[1](k1,k2为任意非负整数),求一个满足等号成立的最小值就行,感觉能用数学公式推出来但太菜了不会,就直接暴力往上加了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxm=1000050;
ll mod;
ll h[2],a[2],x[2],y[2];
ll fir[2];//fir记录第一次到达目标状态的时刻,无法到达则为-1
ll len[2];//len记录循环节长度,如果目标状态不在循环节中则为-1
bool used[maxm];
void calc(){
for(int k=0;k<2;++k){
//计算fir
fir[k]=-1;
memset(used,0,sizeof(used));
ll cur=h[k],cnt=0;
while(1){
if(used[cur]) break;
if(cur==a[k]) { fir[k]=cnt; break; }
++cnt;
used[cur]=true;
cur=(cur*x[k]+y[k])%mod;
}
//从目标状态出发计算len
len[k]=-1;
memset(used,0,sizeof(used));
cur=a[k],cnt=0;
while(1){
if(used[cur]){
if(cur==a[k]) len[k]=cnt;
break;
}
++cnt;
used[cur]=true;
cur=(cur*x[k]+y[k])%mod;
}
}
}
int main(){
scanf("%lld",&mod);
for(int k=0;k<2;++k){
scanf("%lld%lld",&h[k],&a[k]);
scanf("%lld%lld",&x[k],&y[k]);
}
calc();
/*for(int k=0;k<2;++k){
printf("首次出现时间:%d 循环节:%d\n",fir[k],len[k]);
}*/
if(fir[0]==-1 || fir[1]==-1) puts("-1");//根本就没有目标状态
else if(len[0]==-1 && len[1]==-1){//都不在循环节里面,都只会出现一次
if(fir[0]==fir[1]) printf("%lld\n",fir[0]);
else puts("-1");
}
else if(len[0]==-1 && len[1]!=-1){//0号输入的目标状态只出现一次
if(fir[0]>=fir[1] && (fir[0]-fir[1])%len[1]==0) printf("%lld\n",fir[0]);//时间差刚好是循环的整数倍才行
else puts("-1");
}
else if(len[0]!=-1 && len[1]==-1){
if(fir[1]>=fir[0] && (fir[1]-fir[0])%len[0]==0) printf("%lld\n",fir[1]);
else puts("-1");
}
else{//目标状态都处在循环节中,fir[0]+m*len[0]=fir[1]+n*len[1](m,n为任意非负整数) 那就暴力往上加吧,不会用数学式子推
ll ans=-1;
for(ll i=0;i<1e6;++i){
ll tmp=fir[0]+i*len[0]-fir[1];
if(tmp>=0 && tmp%len[1]==0) { ans=tmp+fir[1]; break; }
}
printf("%lld\n",ans);
}
return 0;
}