1. 程式人生 > >【考題 題解】 數字遊戲

【考題 題解】 數字遊戲

題目描述

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 取模,並且當再次出現和原來相同的數字的時候便會有重複的個數,因此必然會出現迴圈。而且如果去模擬,迴圈會消耗大量的時間複雜度,我們同樣可以在時間上尋求優化的辦法。

我們知道,出現重複,會有三種情況
兩者都在兩者進入迴圈之前提前遇到
兩者都在迴圈裡遇到
一開始就相同

如果不會遇到,則同樣有三種情況
一個數字根本不存在
在兩者都進入迴圈節之前沒有遇到且兩者的數字都在迴圈節之前
全都不滿足時

對於在其他情況,我們只需要進行特殊判斷。
對於都出現在迴圈節內的情況,則需要用數序方法。

我們設 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.做列舉的時候確定列舉的範圍不要過小