簡體   English   中英

Facebook黑客杯Subround 1B - 老虎機黑客

[英]Facebook Hacker Cup Subround 1B - Slot Machine Hacker

資料來源:Facebook黑客杯。

我已經嘗試從下面的函數生成一些返回值列表,但似乎無法找到可以預測未來隨機數的原因。 我將如何解決像這樣的問題?

老虎機黑客

您最近結識了一位為老虎機編寫軟件的人。 和他一起閑逛之后,你會注意到他喜歡炫耀他對老虎機如何工作的了解。 最后,讓他讓您詳細描述特定品牌機器上使用的算法。 算法如下:

int getRandomNumber() {
  secret = (secret * 5402147 + 54321) % 10000001;
  return secret % 1000;
}

該函數返回[0,999]中的整數; 每個數字代表在特定機器狀態期間出現在車輪上的十個符號之一。秘密最初設置為某些您不知道的非負值。

通過長時間觀察機器的運行,您可以確定機密值,從而預測未來的結果。 了解未來的結果,您將能夠以聰明的方式下注並贏得大量資金。

輸入輸入的第一行包含正數T,測試用例數。 接下來是T測試用例。 每個測試用例都包含一個正整數N,即你所做觀察的數量。 接下來的N個標記是從0到999的整數,用於描述您的觀察結果。 輸出對於每個測試用例,輸出由空格分隔的機器顯示的下10個值。 如果您的朋友描述的機器無法生成您觀察到的序列,請打印“錯誤的機器”。 如果您無法唯一確定接下來的10個值,請打印“不夠觀察”。

約束條件T = 201≤N≤100輸入中的標記長度不超過3個字符且僅包含數字0-9。

樣本輸入

5
1 968
3 767 308 284
5 78 880 53 698 235
7 23 786 292 615 259 635 540
9 862 452 303 558 767 105 911 846 462

樣本輸出

Not enough observations
577 428 402 291 252 544 735 545 771 34
762 18 98 703 456 676 621 291 488 332
38 802 434 531 725 594 86 921 607 35
Wrong machine

得到它了!

這是我在Python中的解決方案:

a = 5402147
b = 54321
n = 10000001

def psi(x):
    return (a * x + b) % n

inverse1000 = 9990001
max_k = (n-1) / 1000 + 1

def first_input(y):
    global last_input, i, possible_k
    last_input = y
    possible_k = [set(range(max_k))]
    i = 0

def add_input(y):
    global last_input, i
    c = inverse1000 * (b + a * last_input - y) % n
    sk0 = set()
    sk1 = set()
    for k0 in possible_k[i]:
        ak0 = a * k0 % n
        for k1 in range(max_k):
            if (k1 - ak0) % n == c:
                sk0.add(k0)
                sk1.add(k1)
                #print "found a solution"
    last_input = y
    possible_k[i] = possible_k[i] & sk0
    possible_k.append(sk1)
    i += 1
    if len(possible_k[i-1]) == 0 or len(possible_k[i]) == 0:
        print "Wrong machine"
        return
    if len(possible_k[i]) == 1:
        x = y + 1000 * possible_k[i].copy().pop()
        for j in range(10):
            x = psi(x)
            print x % 1000,
        print
        return
    print "Not enough observations"

它可能會被優化(和清理),但由於它在我3歲的筆記本電腦上運行不到30秒,我可能不會打擾它讓它更快...

程序不接受與請求完全相同的輸入,以下是如何使用它:

>>> first_input(767)
>>> add_input(308)
Not enough observations
>>> add_input(284)
577 428 402 291 252 544 735 545 771 34

>>> first_input(78)
>>> add_input(880)
Not enough observations
>>> add_input(53)
698 235 762 18 98 703 456 676 621 291
>>> add_input(698)
235 762 18 98 703 456 676 621 291 488
>>> add_input(235)
762 18 98 703 456 676 621 291 488 332

>>> first_input(862)
>>> add_input(452)
Not enough observations
>>> add_input(303)
Wrong machine
>>> add_input(558)
Wrong machine

如您所見,通常3個觀察值足以確定未來的結果。

由於在文本編輯器中編寫數學內容很痛苦,因此我拍攝了我的 演示 說明:

親手寫的“示范”

由於mod為10,000,001, secret總是在0到10,000,000之間。 由於mod為1,000,觀察到的值始終是secret的最后3位數(剝離前導零)。 所以它是其他未知的數字,只留下10,001個數字來迭代。

對於0..10,000中的每個prefix ,我們首先從prefix的數字構造secret ,然后是帶有前導零的觀察序列中的第一個數字。 如果生成的數字列表等於觀察到的列表,我們就有一個潛在的種子。 如果我們沒有潛在的種子,我們知道這一定是一台錯誤的機器。 如果我們以不止一個結尾,我們沒有足夠的觀察結果。 否則,我們使用單個種子生成接下來的10個值。

這在O(10,000NT)中運行,對於給定的約束是O(20,000,000)。 這是我在C ++中的解決方案的摘錄(借用宏的大量使用,我只在比賽中使用它們):

int N;
cin >> N;
int n[N];
REP(i, N)
  cin >> n[i];
ll poss = 0, seed = -1;
FOR(prefix, 0, 10001) {
  ll num = prefix * 1000 + n[0];
  bool ok = true;
  FOR(i, 1, N) {
    ll next = getRandomNumber(num);
    if (next != n[i]) {
      ok = false;
      break;
    }
  }
  if (ok) {
    poss++;
    seed = prefix * 1000 + n[0];
  }
}
if (poss == 0) {
  cout << "Wrong machine" << endl;
} else if (poss > 1) {
  cout << "Not enough observations" << endl;
} else {
  ll num = seed;
  FOR(i, 1, N)
    getRandomNumber(num);
  REP(i, 10)
    cout << getRandomNumber(num) << " ";
  cout << endl;
}

這里有什么名字? 更新秘密的公式和觀察列表。 什么是未知的? 起始秘密值。

最初的秘密是什么? 我們可以將可能的起始秘密限制為10,000個可能的值,因為觀察值是secret % 1000 ,最大秘密是10,000,000。

然后可能的起始秘密

possible = [1000 * x + observed for x in xrange(10001)]

只有這些秘密的子集(如果有的話)才會更新為顯示下一個觀察值的值。

def f(secret):
    return (secret * 5402147 + 54321) % 10000001

# obs is the second observation.
new_possible = [f(x) for x in possible]
new_possible = [x for x in new_possible if x % 1000 == obs]

即使每個possible值仍然是new_possible ,我們只會檢查每個觀察的10,000個數字。 但是,很多值不太可能匹配多個觀察結果。

只是繼續迭代該過程,並且可能的列表將為空,長於一個,或者它將只有一個答案。

這是一個將所有內容組合在一起的功能。 (你需要上面的f

def go(observations):
    if not observations:
        return "not enough observations"

    # possible is the set of all possible secret states.
    possible = [x * 1000 + observations[0] for x in xrange(10001)]

    # for each observation after the first, cull the list of possible
    # secret states.
    for obs in observations[1:]:
        possible = [f(x) for x in possible]
        possible = [x for x in possible if x % 1000 == obs]
        # uncomment to see the possible states as the function works 
        # print possible

    # Either there is 0, 1, or many possible states at the end.
    if not possible:
        return "wrong machine"
    if len(possible) > 1:
        return "not enough observations"

    secret = possible[0]
    nums = []
    for k in xrange(10):
        secret = f(secret)
        nums.append(str(secret % 1000))
    return " ".join(nums)

import sys

def main():
    num_cases = int(sys.stdin.readline())

    for k in xrange(num_cases):
        line = [int(x) for x in sys.stdin.readline().split()]
        print go(line[1:])

main()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM