题意:每次从[2,M]中随机选一个数写在卡牌上,重复N次;再从N个卡牌中任意选择x(0<=x<=N)个,每个卡牌被选的概率为1/2,计算卡牌上数的乘积;重复K次,则我们有K个乘积;现给出K个乘积,请猜测卡牌上的数。
原题:https://code.google.com/codejam/contest/2418487/dashboard#s=p2;
官方解答:https://code.google.com/codejam/contest/2418487/dashboard#s=a&a=2
首先做了14年round1的最后一道随机题,感觉比较有趣,就做了一下这道13年的随机题,有点难啊。
题解:如官方解答,这种题看似随机,其实都有其固定解法,当然解法可能不唯一。
一、贝叶斯
这种题一般都会选择贝叶斯算法,或者最大后验概率。假设一种组合为A,一组乘积为P,则最可能的组合A’ = max{Proba(Ai|P),Ai},而Proba(Ai|P) = Proba(P|Ai)*Proba(Ai)/Proba(P)。对于不同Ai,Proba(P)都是一样的,但是分子的Proba(P|Ai)和Proba(Ai)是不一样的,我们就是要计算这个。
二、概率计算
- 计算Proba(Ai),要先知道所有的Ai,对于乘积结果来说,Ai中数字的顺序是无关紧要的,可以用DFS搜索可能的Ai并保存,搜索过程中保持数字递增,相同数字连续。
- 卡牌中乘积结果的条件概率则可以直接以二进制位代表N张卡牌的选择,枚举这2^N中等概率的选择。
- 对于每组乘积,计算出每种组合产生这组乘积的概率,选出最大概率的组合。
注意,为了能直接根据乘积找出它的条件概率,又因为乘积最大为8^12,可以采用hash散列处理。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define maxP 4157
vector<vector<int> > cards;
vector<int> tmpCards;
vector<double> cardsProba; //每种卡牌出现的概率
int fact[15];
int cardsNum;
int R,N,M,K;
struct node
{
long long product;
int proba;
node* next;
}*** conditionProba; //每种乘积对应卡牌上的数出现的概率
int generateCards(int beginPos,int digit,double tmpProba)
{
if(beginPos == N)
//卡牌已写完
{
cards.push_back(tmpCards);
cardsNum++;
cardsProba.push_back(tmpProba);
return 0;
}
if(digit == M+1)
//卡牌没写完,但是已经不能在写其他数
return 0;
for(int i = beginPos;i < N;i++)
{
for(int j = digit;j <= M;j++)
//卡牌上的数要递增地写,防止冗余
{
for(int k = beginPos;k <= i;k++)
//将beginPos到i的卡牌写上j
{
tmpCards[k] = j;
}
generateCards(i+1,j+1,tmpProba/fact[i-beginPos+1]);
}
}
return 0;
}
//产生阶乘计算概率
int generateFact()
{
fact[1] = 1;
for(int i = 2;i <= N;i++)
{
fact[i] = fact[i-1]*i;
}
return 0;
}
node* hashCheck(node* in,long long product)
{
node* tmpNode;
for(tmpNode = in;tmpNode != NULL;tmpNode = tmpNode->next)
{
if(tmpNode->product == product)
{
break;
}
}
return tmpNode;
}
int hashInsert(node** in,long long product)
{
int slot = product%maxP;
node* tmpNode = hashCheck(in[slot],product);
if(tmpNode == NULL)
//如果没有product,则新生成节点加入链表
{
tmpNode = new node;
tmpNode->product = product;
tmpNode->proba = 1;
tmpNode->next = in[slot];
in[slot] = tmpNode;
}
else
{
(tmpNode->proba)++;
}
return 0;
}
int generateConditionProba()
{
long long product;
int maxDigit = (1<<N)-1;
conditionProba = new node**[cardsNum];
int i,j,k;
for(k = 0;k < cardsNum;k++)
{
conditionProba[k] = new node*[maxP];
memset(conditionProba[k],0,sizeof(node*)*(maxP));
for(i = 0;i <= maxDigit;i++)
//枚举2^N种卡牌乘法
{
product = 1;
for(j = 0;j < N;j++)
{
if(i&(1<<j))
{
product *= (long long)cards[k][j];
}
}
hashInsert(conditionProba[k],product);
}
}
return 0;
}
int main()
{
int T;
long long product[15];
int i,j,t;
double maxProba;
double tmpProba;
node* tmpNode;
int maxProbaIndex;
scanf("%d",&T);
for(t = 1;t <= T;t++)
{
scanf("%d%d%d%d",&R,&N,&M,&K);
cardsNum = 0;
tmpCards.resize(N);
generateFact();
generateCards(0,2,fact[N]);
generateConditionProba();
printf("Case #%d:\n",t);
while(R--)
{
maxProba = -1;
for(i = 0;i < K;i++)
{
scanf("%lld",product+i);
}
for(i = 0;i < cardsNum;i++)
{
tmpProba = cardsProba[i];
for(j = 0;j < K;j++)
{
tmpNode = hashCheck(conditionProba[i][product[j]%maxP],product[j]);
if(tmpNode == NULL)
{
tmpProba = 0;
break;
}
else
{
tmpProba *= ((double)tmpNode->proba);
}
}
if(maxProba < tmpProba)
{
maxProba = tmpProba;
maxProbaIndex = i;
}
}
for(i = 0;i < N;i++)
{
printf("%d",cards[maxProbaIndex][i]);
}
printf("\n");
}
delete[] conditionProba;
}
return 0;
}