[Google Code Jam] 2013-1A-C Good Luck 解法

问题的陈述在:https://code.google.com/codejam/contest/2418487/dashboard#s=p2&a=1,

官方的分析在:https://code.google.com/codejam/contest/2418487/dashboard#s=a&a=2。

这篇文章是结合官方的分析以及Dlougach的solution总结的解题思路。

1. 列举所有的可能的数字组合

由[2,M]区间内的数字组成长度为N的数组组合,本身有(M-1)N可能性,因为组合中的每一个数字都可以从[2,M]这M-1个数字中随机选取。

但是由于这些数字组合本身不存在顺序的问题,比如(5, 2, 3, 2)和(2, 2, 3, 5)是相同的,所以可以排除掉很多相同的情况。

可以编程列举出所有的数字组合,思路是从N个2的数字组合开始,顺序加1,直到N个M的组合。代码如下:

 1 // all possible combinations
 2 vector<vector<int>> digits;
 3 vector<int> dig(N, 2);
 4 while (dig != vector<int>(N, M))
 5 {
 6     digits.push_back(dig);
 7     int i = N-1;
 8     while (dig[i] == M) i--;
 9     dig[i]++;
10     for (int j = i + 1; j < N; j++)
11         dig[j] = dig[i];
12 }
13 digits.push_back(dig);
14 int total = digits.size();

经过所有的列举,共有18564种可能的数字组合。

2. 计算每一种数字组合出现的概率。

当给出的所有的K个products都为1的时候应该选择哪一个数字组合呢?选择N个2还是其他?

要注意到,即便给出的所有K个products都为1,由于每一种数字组合出现的概率也有区别,也可以从中选取出可能出现概率最高的数字组合。

对于一个数字组合,计算这个数字组合可能出现的概率:

比如M = 8, N = 12的情况下,222333445558出现的可能性为:(C(12,3)*C(9,3)*C(6,2)*C(4,3)*C(1,1)) / (8-1)12,即在所有的12个位置中找3个位置摆放2,再在剩下的9个位置中找3个位置摆放3,再在剩下的6个位置中找2个位置摆放4.....最后除以总数(8-1)12

其中C(n,k)就是我们在学概率时学到的Cnk

所以要想求概率,首先需要统计出每个数字重复出现的次数,具体代码如下:

 1 vector<double> prob(total, 0);
 2 for (int i = 0; i < total; ++i)
 3 {
 4     vector<int> count(M+1, 0);
 5     for (int j = 0; j < N; ++j)
 6         count[digits[i][j]]++;
 7     double p = 0;
 8     int size = N;
 9     for (int j = 2; j <= M; ++j)
10     {
11         p += log(C[size][count[j]]);
12         size -= count[j];
13     }
14     prob[i] = p;
15 }

这段代码中有两点技巧:一是C(n,k)的计算方法,二是取对数(log)问题。

a) C(n,k)的计算

C(n,k)本身有一个计算公式是我们很熟悉的:C(n,k) = n! / (k!(n-k)!)。这样计算牵涉到阶乘,考虑到要计算多个C(n,k),为了重复利用计算好的结果,这里有一个比较简单的方法,就是杨辉三角型(Pascal's triangle)以及。

首先构建一个杨辉三角形存放于二维数组C中,如下图所示。这样的摆放有一个规律C[n][m] = C[n-1][m-1]+C[n-1][m],恰好是C(n,k)的另一种计算方法。

              m = 0    m = 1    m = 2    m = 3    m = 4    m = 5...

n = 0          1           0           0            0           0           0...

n = 1          1           1           0            0           0           0...

n = 2          1           2           1            0           0           0...

n = 3          1           3           3            1           0           0...

n = 4          1           4           6            4           1           0...

这样一来只要提取数组元素C[n][k],就是C(n,k)的值,比如C(4,2) = C[4][2] = 6。数组搭建过程实现如下:

1 double C[13][13];
2 memset(C, 0, sizeof(C));
3 C[0][0] = 1;
4 for (int i = 1; i < 13; ++i)
5 {
6     C[i][0] = 1;
7     for (int j = 1; j <= i; ++j)
8         C[i][j] = C[i-1][j-1] + C[i-1][j];
9 }
b) 取对数(log)问题

对数运算有一个性质:log(ABC...Z)=log(A)+log(B)+log(C)...log(Z)。

如果要比较几个元素的大小(所有元素都大于0),并且每个元素都由多个数的乘积构成,则可以简化为判断所有元素对数的大小(取对数不会影响增减性),这样一来对于每个元素来讲,并不需要计算多个数的乘法了,而是这些数取对数之后的加法。

这样计算有一个很大的好处,就是乘法运算本身很容易导致结果溢出,取对数变加法之后可以避免溢出,并且大大减小运算范围。

3. 对于每一种数字组合,列举所有可能出现的products,并对其进行计数

用一个mask,从1到1<<N遍历一遍,mask的二进制为1的相应位将参与product计算。比如mask为21,二进制表示为10101,那么该数字组合中,将从右数起的第1、3、5个数字相乘得到product。

 1 vector<unordered_map<long long, int>> products(total);
 2 for (int i = 0; i < total; ++i)
 3 {
 4     for (int mask = 1; mask < (1<<N); ++mask)
 5     {
 6         long long prod = 1;
 7         for (int j = 0; j < N; ++j)
 8             if (mask & (1<<j))
 9                 prod *= digits[i][j];
10         products[i][prod] += 1;
11     }
12 }

以上三步都是进行pre-computation,下面就是真正运行测试数据了。

4. 遍历每一种数字组合,计算该数字组合出现的概率,最后选取概率最高者输出。 

给定product 1, 2, 3, ..., K的情况下,数字组合A出现的概率p(A) = A本身出现的概率prob[A] * product 1出现的概率products[A][product1] * .... * product K出现的概率products[A][product K]。

由于这些算好的概率都已经取号对数,所以应该用加法。

 1 while (R--)
 2 {
 3     vector<int> test_products;
 4     for (int i = 0; i < K; ++i)
 5         test_products.push_back(rll());
 6     int res_i = 0;
 7     double resProb = INT_MIN;
 8     for (int i = 0; i < total; ++i)
 9     {
10         double p = prob[i];
11         for (int k = 0; k < K; ++k)
12         {
13             if (test_products[k] == 1) break;
14             unordered_map<long long, int>::iterator it = products[i].find(test_products[k]);
15             if (it != products[i].end())
16             {
17                 p += log((double)it->second);
18             }
19             else
20             {
21                 p = INT_MIN;
22                 break;
23             }
24         }
25         if (resProb < p)
26         {
27             res_i = i;
28             resProb = p;
29         }
30     }
31     for (int i = 0; i < digits[res_i].size(); ++i)
32         printf("%d", digits[res_i][i]);
33     printf("\n");
34 }

这道题完整的代码可以在下面的链接获取:https://github.com/AnnieKim/ForMyBlog/blob/master/20130508/1A-C-Good%20Luck.cpp

原创文章,转载请注明出处:http://www.cnblogs.com/AnnieKim/archive/2013/05/08/3059614.html。

转载于:https://www.cnblogs.com/AnnieKim/archive/2013/05/08/3059614.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/408886.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

oracle 安装display,Linux安装Oracle 11时报错DISPLAY解决方案

在Linux上安装Oracle时&#xff0c;经常会报以下错误&#xff1a;无法使用命令 /usr/X11R6/bin/xdpyinfo 自动检查显示器颜色。请检查是否设置了DISPLA在Linux上安装Oracle时&#xff0c;经常会报以下错误&#xff1a;无法使用命令 /usr/X11R6/bin/xdpyinfo 自动检查显示器颜色…

机器学习入门系列(2)--机器学习概览(下)

这是本系列的第二篇&#xff0c;也是机器学习概览的下半部分&#xff0c;主要内容如下所示&#xff1a; 文章目录1. 机器学习的主要挑战1.1 训练数据量不足1.2 没有代表性的训练数据1.3 低质量的数据1.4 不相关的特征1.5 过拟合1.6 欠拟合2. 测试和评估3. 小结1. 机器学习的主要…

[实战] 图片转素描图

本文大约 2000 字&#xff0c;阅读大约需要 6 分钟 我们知道图片除了最普通的彩色图&#xff0c;还有很多类型&#xff0c;比如素描、卡通、黑白等等&#xff0c;今天就介绍如何使用 Python 和 Opencv 来实现图片变素描图。 主要参考这篇文章来实现–How to create a beautifu…

打印水仙花数oracle,javaScript实现回文数、水仙花数判断和输出斐波那契数列

javaScript实现回文数、水仙花数判断和输出斐波那契数列发布时间&#xff1a;2020-07-22 01:15:37来源&#xff1a;51CTO阅读&#xff1a;422作者&#xff1a;Cynthia_xie// 判断一个数是不是回文数// 方法一&#xff1a;先将数字转换成字符串&#xff0c;然后依次判断第一个和…

[实战]制作简单的公众号二维码关注图

本文大约 1933 字&#xff0c;阅读大约需要 6 分钟 最近刚刚更换了公众号名字&#xff0c;然后自然就需要更换下文章末尾的二维码关注图&#xff0c;但是之前是通过 windows 自带的画图软件做的&#xff0c;但是之前弄的时候其实还是比较麻烦的&#xff0c;所以我就想作为一名程…

155个建议笔记--建议33:不要覆写静态方法

我们知道在JAVA中可以通过overRide来增强或减弱父类的方法和行为&#xff0c;但覆写是针对非静态方法的&#xff0c;不能针对静态方法&#xff08;也叫类方法&#xff09;&#xff0c;为什么呢&#xff1f;看一下下面的例子&#xff1a; View Code public class OverRideTepubl…

linux初始化进程ppid号,linux基础(十一)--系统初始化的简谈

我们在深入学习linux之前呢首先要了解其的引导加载过程&#xff0c;这样我们就可以在判断一些在系统初始化过程的出现问题的来源&#xff0c;并及时做出处理。这个过程大概分为【开机】——【BIOS】(CMOS)——【grub或者其他引导程序】——【kernel boot】(initrd文件)——【in…

Vim 快速入门

本文大约 5000 字&#xff0c; 阅读大约需要 10 分钟在 Linux 下最常使用的文本编辑器就是 vi 或者 vim 了&#xff0c;如果能很好掌握这个编辑器&#xff0c;非常有利于我们更好的在 Linux 下面进行编程开发。vim 和 viVim是从 vi 发展出来的一个文本编辑器。代码补完、编译及…

(转载)Qt中使用cout输出的方法

&#xff08;转载&#xff09;http://blog.sina.com.cn/s/blog_4f183d960100sdxf.html最近用QT写一个控制台程序&#xff0c;却不能将提示文本输出到屏幕。 cout<<"abcd"正常运行但是屏幕上却没有输出。 解决办法&#xff1a; 在qt的工程文件(.pro文件)中加入以…

linux如何给vm权限,linux – 如何创建一个每个用户的vm被隔离的环境

感谢libvirt的PolicyKit支持,我相信这可以根据您需要的功能来完成.规则可以由您创建,并由PolicyKit ACL作为数据库(文件,服务器等)访问,以获取有关所有者的信息.此外,如果PolicyKit规则具有对数据库的写入权限,则可以在创建VM时将VM分配给各自的所有者,从而自动创建该数据库.它…

Vim快速入门

本文大约 5000 字&#xff0c; 阅读大约需要 10 分钟 在 Linux 下最常使用的文本编辑器就是 vi 或者 vim 了&#xff0c;如果能很好掌握这个编辑器&#xff0c;非常有利于我们更好的在 Linux 下面进行编程开发。 vim 和 vi Vim是从 vi 发展出来的一个文本编辑器。代码补完、编…

linux内核 cpu_die,Linux内核Crash分析

结合上面的知识&#xff0c;看下当内核打印堆栈信息时&#xff0c;都打印了上面信息。下面的打印信息是工作中遇到的一种情况&#xff0c;打印了内核的堆栈信息&#xff0c;PC指针在dev_get_by_flags中&#xff0c;不能访问的内核虚地址为45685516&#xff0c;内核中一般可访问…

javascript设计模式--命令模式

1 <!DOCTYPE html>2 <html>3 <head>4 <title>命令模式</title>5 <meta charset"utf-8">6 </head>7 <body>8 9 <script>10 /**11 * 命令模式12 *13 * 定义&#xff1a;14 * 将一个请求封装为一个对…

linux 全球用户数量,全球Linux用户市场占有率升至2.78%

6月7日下午1点左右&#xff0c;www.w3counter.com终 于发布了今年5月份的统计数字&#xff0c;看起来&#xff0c;该网站为此次发布大概做了不少审定工作&#xff0c;相比以往&#xff0c;拖延了几天才发布。公布的统计数字如下&#xff1a;4月份 5月份XP …

[GAN学习系列] 初识GAN

本文大约 3800 字&#xff0c;阅读大约需要 8 分钟 要说最近几年在深度学习领域最火的莫过于生成对抗网络&#xff0c;即 Generative Adversarial Networks(GANs)了。它是 Ian Goodfellow 在 2014 年发表的&#xff0c;也是这四年来出现的各种 GAN 的变种的开山鼻祖了&#xff…