zoukankan      html  css  js  c++  java
  • Good Luck Googlecodejam 2013Round1A yongmou

    这个题目还是很有意思,它没有给出解决问题所需的全部信息,而是只给了部分信息,来猜测正确答案是什么。在一定的概率下会猜测到正确的结果。

    1 问题描述

    从2到M中随机选择N个数,允许重复。
    再从这N个数中随机选择它的一个子集,计算出这个子集中元素的乘积$p_1$。
    重复上一步,直到得到了K个乘积$p_1,p_2,…,p_k$。
    要求:给出这K个乘积,推测原来的N个数
    具体的输入输出要求,不详细介绍了,到codejam网站上看去吧

    2 解决方案

    从2到M中选择N个数,若考虑顺序那么就有$(M-1)^N$种,若不考虑顺序就大大减少了。

    对于题目的第二个输入N=12, M=8, 不考虑顺序就只有18564种,不算太大。
    那么,我们的任务就是根据这K个乘积,选出概率最大的一种组合
    现在我们计算在乘积为$p_1,p_2,…,p_k$条件下,原组合为C的概率: $$ P(C|p_1,p_2,...,p_k) = \frac{P(C)*P(p_1,p_2,...,p_k|C)}{P(p_1,p_2,...,p_k)} $$ 这个就是贝叶斯概率公式,一般形式写作:$ P(A|B)=\frac{P(A)P(B|A)}{P(B)} $ 机器学习中有一类重要的分类方法就是基于这个贝叶斯公式。

    由于我们的目标是找出概率最大的C,所以不必把这个公式中的所有的项都计算出来,例如分母为P(p1,p2,…,pk)对于特定的K个乘积在所有的组合下都是不变的。
    只需要计算$P(C)$和\(P(p_1,p_2,\ldots,p_k|C)\), 对于由于组合C,由于不考虑顺序那么,每个C被取得概率就不等了,比如234会出现6次,而333只会出现一次,计算这个P(C): $$ P(C)= \frac{N!/(c_2!c_3!\ldots c_m!)}{(M-1)^N}$$ 其中$c_2,c_3,\ldots,c_i$表示组合中数字i出现的次数

    $$ P(p_1,p_2,\ldots,p_k|C) = P(p_1|C)P(p_2|C)\ldots P(p_k|C)$$

    其中$P(p|C) = \frac{元素积为p的子集个数}{2^N}$
    枚举C的所有子集就可以计算出上述$P(p|C)$。
    现在就知道怎么实现了:对于所有的组合计算条件概率,从中选出概率最大的一个组合。 由于会有很多组乘积,需要进行推测,所以这里将会有大量的重复计算,所以,最好将P和P(p|C)全部提前计算出来。

    3 实现和结果分析

     

    3.1 预计算

    预计算:计算出某个组合的概率,和这种组合下子集积的概率

    # coding: UTF-8
    import cPickle as pickle
    from array import array
    from sys import stdout
    from sys import stderr
    from random import randint
    
    def fact(n):
        if n == 0:
            return 1
        return n * fact(n-1)
    
    def probability(nums):
        p = fact(len(nums))
        i = 1
        c = 1
        while i < len(nums):
            if (nums[i] == nums[i-1]):
                c += 1
            else:
                p /= fact(c)
                c = 1
            i += 1
        p /= fact(c)
        return p
    
    gcount = 0
    def pre_compute(N, M):
        fo = open('dump.dat', 'w')
        nums = array('i', xrange(N))
        pset = {}
    
        def subset(d, p):
            if d >= N:
                if p in pset:
                    pset[p] += 1
                else:
                    pset[p] = 1
            else:
                subset(d+1, p)
                subset(d+1, p*nums[d])
    
        def search(d, lt):
            global gcount
            if d >= N:
                gcount += 1
                if (gcount % 100) == 0:
                    print "generated %d.." % gcount
                pickle.dump(nums, fo)
                pickle.dump(probability(nums), fo)
                pset.clear()
                subset(0, 1)
                pickle.dump(pset, fo)
            else:
                for i in xrange(lt, M+1):
                    nums[d] = i
                    search(d+1, i)
    
        search(0, 2)
        fo.close()
    

    预计算大概需要1m多,而载入只需要2s左右,还是节省了很多时间的

    载入的代码:

    def load():
        fo = open("dump.dat")
        count = 0
        table = []
        while True:
            try:
                nums=pickle.load(fo)
                prob=pickle.load(fo)
                pset=pickle.load(fo)
                table.append([nums, prob, pset])
                count += 1
                if (count % 1000) == 0:
                    stderr.write("loaded %d..\n" % count)
            except EOFError:
                break
        return table
    

    3.2 处理过程,遍历所有的组合找到概率最大的组合

    def process_case(case):
        table = load()
    
        (R, N, M, K) = map(int, raw_input().split())
        print "Case #%d:" % case
        for r in xrange(R):
            products = map(int, raw_input().split())
            max_prob = -1
            nums = []
            for item in table:
                prob = item[1]
                pset = item[2]
                for p in products:
                    if p not in pset:
                        prob = 0
                        break
                    prob *= pset[p]
                if prob > max_prob:
                    max_prob = prob
                    nums = item[0]
    
            for n in nums:
                stdout.write(str(n))
            stdout.write("\n")
    

    3.3 自己构造输入输出

    其实这个题是可以自己构造输入的,然后自己写个judge的程序, 对于第二种输入,阈值要求是1120,自己构造输入,然后用上面的程序得到猜测结果, 大概猜对了1300左右。

    构造输入和judge代码如下:

    # 生成样例数据
    def generate(R, N, M, K):
        fi = open('input.txt', 'w')
        fa = open('right-answer.txt', 'w')
        fi.write("1\n%d %d %d %d\n" % (R, N, M, K))
        nums = array('i', xrange(N))
        for i in xrange(R):
            for j in xrange(N):
                nums[j] = randint(2, M)
                fa.write("%d" % nums[j])
            fa.write('\n')
            for j in xrange(K):
                p = 1
                for n in nums:
                    if randint(0, 1) == 1:
                        p *= n
                fi.write("%d " % p)
            fi.write('\n')
        fi.close()
        fa.close()
    
    def judge(submit_file, answer_file, R):
        fs = open(submit_file)
        fa = open(answer_file)
    
        fs.readline()
        count = 0
        for i in xrange(R):
            ls = list(fs.readline().strip())
            la = list(fa.readline().strip())
            ls.sort()
            la.sort()
            if ls == la:
                count += 1
        fs.close()
        fa.close()
        return count
    

    4 总结

    谷歌的出题思路跟技术的发展趋势是一致的,基于统计的机器学习方法有很广泛的应用。 这个题就是使用贝叶斯公式(条件概率公式)进行推断。


    ps1: 上面的python代码在我的机器上运行3m,而题目要求是四分钟内提交答案,时间有点紧,勉强够用。使用C++会更快的。不过,我后来找到了一个叫pypy的python解释器,它的速度比python官方的解释器快好多,一分钟就算出结果了。
    ps2: 吐嘈下python

    def f():
        x = 1
        def g():
            x += 1
        g()
    

    调用f()就会出错,直到python3才给出个nonlocal关键字解决这个问题。
    关于这点,我只想说,设计语言也太不专业了。。。

    Date: 2013-05-10 Fri

    Author: liyongmou

    Org version 7.9.2 with Emacs version 24

    Validate XHTML 1.0
  • 相关阅读:
    阿里云的一道面试题:写一个爬取文档树和通过输入关键字检索爬取的内容的demo
    linux配置SVN,添加用户,配置用户组的各个权限教程
    logback的使用和配置|logback比log4j的优点|logback是一个更好的log4j
    [已解决]mysql查询一周内的数据,解决一周的起始日期是从星期日(星期天|周日|周天)开始的问题
    MySql-----InnoDB记录存储结构-----1
    Mysql----字符集和比较规则
    Mysql-----启动和配置文件-----2(未完,待续)
    MySql----前言有点用----1
    Java高并发--------并行模式和算法(需要看更多的东西,才能总结)---------5
    Java高并发------锁优化及注意事项--------4
  • 原文地址:https://www.cnblogs.com/liyongmou/p/3072157.html
Copyright © 2011-2022 走看看