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

时间:2011-01-27 04:08:33

标签: algorithm random

来源:Facebook黑客杯。

我已经尝试从下面的函数生成一些返回值列表,但似乎无法找到可以预测未来随机数的原因。我该如何解决像这样的问题?

老虎机黑客

您最近结识了一位为老虎机编写软件的人。和他一起闲逛之后,你会注意到他喜欢炫耀他对老虎机如何工作的了解。最后,让他让您详细描述特定品牌机器上使用的算法。算法如下:

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

此函数返回[0,999]中的整数;每个数字代表在特定机器状态期间出现在车轮上的十个符号之一。秘密最初设置为某些您不知道的非负值。

通过长时间观察机器的操作,您可以确定机密值,从而预测未来的结果。了解未来的结果,您将能够以聪明的方式下注并赢得大量资金。

输入 输入的第一行包含正数T,即测试用例的数量。接下来是T测试用例。每个测试用例都包含一个正整数N,即你所做观察的数量。接下来的N个标记是从0到999的整数,用于描述您的观察结果。 产量 对于每个测试用例,输出由空格分隔的机器显示的下10个值。 如果您的朋友描述的机器无法生成您观察到的序列,请打印“错误的机器”。 如果您无法唯一确定接下来的10个值,请改为打印“Not enough observation”。

约束 T = 20 1≤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

3 个答案:

答案 0 :(得分:5)

知道了!

这是我在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个观察值足以确定未来的结果。

由于在文本编辑器中编写数学内容很痛苦,我拍了一张我的演示解释:

hand written "demonstration"

答案 1 :(得分:1)

由于mod为10,000,001,

secret总是在0到10,000,000之间。由于mod为1,000,观察到的值始终是secret的最后3位数(前导零被剥离)。所以这是其他未知的数字,只留下10,001个数字来迭代。

对于0..10,000中的每个prefix,我们首先从secret的数字构建prefix,然后是带有前导零的观察序列中的第一个数字。如果生成的数字列表等于观察到的列表,我们就有一个潜在的种子。如果我们没有潜在的种子,我们知道这一定是一台错误的机器。如果我们以不止一个结尾,我们没有足够的观察结果。否则,我们使用单个种子生成接下来的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;
}

答案 2 :(得分:1)

这里有什么消息?更新秘密的公式和观察列表。什么是 未知?起始秘密值。

最初的秘密是什么?我们可以限制可能的开始 对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 每次观察的数字。但是,许多价值观不太可能匹配 多次观察。

继续迭代该过程,并且可能的列表将为空,长于1, 或者它只有一个答案。

这是一个将所有内容组合在一起的功能。 (您需要上面的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()