【考题 题解】 数字游戏

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Ronaldo7_ZYB/article/details/81838424

题目描述

HL 中学茶余饭后喜欢玩游戏,一个游戏规则如下:
共两人参加游戏,若第一个人当前手中的数为 w1,则下一秒他手上的数将会变成(x1w1 + y1) mod m;若第二个人当前手中的数为 w2,则下一秒他手上的数将会变为(x2w2 + y2) mod m。a mod b 表示 a 除以 b 的余数。
第 0 秒,两个人手上的数分别为 h1, h2; 请求出最快在第几秒,第一个人手上的数为 a1 并且同时第二个人手上的数为 a2。若不可能,则输出-1。
输入格式
输入包含 5*T+1 行。
第一行为一个正整数 T,表示数据组数。
对于接下来的每一组数据,第一行为一个正整数 m,第二行包括两个整数h1, a1,第三行包括两个整数 x1, y1,第四行包括两个整数 h2, a2,第五行包括两个整数 x2, y2。

输出格式

输出包含 T 行。
对于每一组数据,输出一行,一个整数,如题所述。

样例数据

input

2
5
4 2
1 1
0 1
2 3
1023
1 2
1 0
1 2
1 1

output

3
-1

数据规模与约定

对于 30%的数据:m<=1000
对于 100%的数据:T<=5, h1≠a1 且 h2≠a2,2<=m<=10^6,0<=h1,a2,x1,y1,h2,a2,x2,y2

时间限制:

1s1s

空间限制:

256MB


数字游戏具体思路

显然,如果使用暴力则不断枚举即可。但是显然会超市,我们必须要用数学方法去解决这一个问题。
至于如何数学,我们不妨举一个例子来进行理解。

例如有这么一串数字: 1 2 3 4 3 4 3 4..........

相信你一定可以看出,出现了不断的循环。这是为什么?因为每做一次运算都必须要对一个数 m 取模,并且当再次出现和原来相同的数字的时候便会有重复的个数,因此必然会出现循环。而且如果去模拟,循环会消耗大量的时间复杂度,我们同样可以在时间上寻求优化的办法。

我们知道,出现重复,会有三种情况
两者都在两者进入循环之前提前遇到
两者都在循环里遇到
一开始就相同

扫描二维码关注公众号,回复: 3652826 查看本文章

如果不会遇到,则同样有三种情况
一个数字根本不存在
在两者都进入循环节之前没有遇到且两者的数字都在循环节之前
全都不满足时

对于在其他情况,我们只需要进行特殊判断。
对于都出现在循环节内的情况,则需要用数序方法。

我们设 s t e p 1 为进入循环节前的长度, f i r s t 1 表示第一次出现 a 1 的位置, l e n 1 为循环节的长度。 s t e p 2 f i r s t 2 l e n 2 相同。再设 k 1 为第一个数列进入循环节的圈数, k 2 k 1 。因此我们只需要满足下列等式:

k 1 l e n + f i r s t 1 = k 2 l e n + f i r s t 2

输出最小的数值即可。

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define x1 X1
#define x2 X2
#define y1 Y1
#define y2 Y2
long long tag1[1200000]={},tag2[1200000]={};
long long m,h1,a1,x1,y1,h2,a2,x2,y2;
void Readit()
{
    cin>>m>>h1>>a1>>x1>>y1>>h2>>a2>>x2>>Y2;
}
void Work()
{
    memset(tag1,0,sizeof(tag1));
    memset(tag2,0,sizeof(tag2));

    Readit();
    long long w1=h1,w2=h2;
    if (h1==a1 && h2==a2) { cout<<"0\n"; return; }

    long long len1=0,len2=0,step1=0,step2=0,first1=0,first2=0;
    for (register long long i=1;;++i)
    {
        w1=(w1*x1+y1)%m;
        w2=(w2*x2+y2)%m;//数值更新 

        if (!tag1[w1]) tag1[w1]=i;
        else if (!len1) len1=i-tag1[w1],step1=tag1[w1]-1;
        if (!tag2[w2]) tag2[w2]=i;
        else if (!len2) len2=i-tag2[w2],step2=tag2[w2]-1;//标记数组 

        if (w1==a1 && w2==a2) { cout<<i<<"\n"; return; }//如果刚好相等

        if (w1==a1) first1=i;
        if (w2==a2) first2=i;//判断第一次出现在数列中的位置

        if (len1 && len2) break; 
    }

    if (first1<=step1 || first2<=step2 || !first1 || !first2) { cout<<"-1\n"; return; }
    //如果第一次出现的位置是不在循环内的&根本没有出现过//

    for (int k1=0;k1<=1000000;k1++)
    {
        long long num=len1*k1+first1-first2;
        long long k2=num/len2;
        if (k1*len1+first1==k2*len2+first2) 
        {
            cout<<k1*len1+first1<<"\n";
            return;
        }
    }

    cout<<"-1\n";
    return;
}
int main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    long long T;cin>>T;
    while (T--) Work();
    fclose(stdin);fclose(stdout);
    return 0;
}

总结

1.开long long
2.避免数组开小
3.做枚举的时候确定枚举的范围不要过小

猜你喜欢

转载自blog.csdn.net/Ronaldo7_ZYB/article/details/81838424
今日推荐