2019xupt-acm校赛 题解 ( F.猜球球 ) by出题组tongtong

重现赛链接 2019 ACM ICPC Xi'an University of Posts & Telecommunications School Contest

前面的话

有幸参与2019XUPT-ACM校赛出题和裁判工作。过程还是蛮有意思的。

转载请注明出处和链接。

正文

                       F-猜球球(3s)

Description

六一到了,为了庆祝这个节日,好多商家都推出了很多好玩的小游戏。Tongtong看到了一个猜球球的游戏,有n种除了颜色之外完全相同的球,商家从中拿出来一个球球放到了箱子里,已知第i种颜色的球出现在箱子里的概率为ai。Tongtong可以用下面这种方法来确定箱子中球的颜色:向商家提出猜测:“是第x种颜色的球球或第y种颜色的球球或...........中的一个”,商家会回答你的猜测是正确还是错误的,直到你有百分百的把握确定箱子里的球球,猜测的次数越少,Tongtong能够得到的礼物就更好。为了让Tongtong过一个开开心心的六一,请你找出一种最优的策略,尽可能少的向店主提出猜测来确定球的颜色,输出猜测次数的期望值。

策略“最优”是指:猜测次数的期望最小。当你有百分百的把握确定箱子里的球球颜色种类时,则不需要继续猜测。例如,如果有两种颜色的球球,箱子里放的是第二种颜色的球球,你可以猜测“是第一种颜色的球球”。商家会告诉你“错误”,所以你可以推测“箱子里的球球是第二种颜色的”,并且有百分百的把握,所以你就可以结束猜测而不需要额外的一次猜测。这种询问方式猜测次数为一次(不管你这一次有没有猜对)。

Input

第一行输入一个n,表示有n种颜色的球。 n<=2 000

第二行输入n个非负小数a1~an,表示是第i种颜色球球的概率,保证加和起来为1。

Output

输出最小期望,保留7位小数。

Sample Input1

3

0.5000000000 0.2500000000 0.2500000000

Sample Output1

1.5000000

Sample Input2

4

0.3 0.3 0.2 0.2

Sample Output2

2.0000000

 

Hint

对于样例1:

最佳策略下:第一次询问“是不是第二种颜色的球球或第三种颜色的球球中的一种”,如果回答“否”则可以知道是第一种颜色的球球,结束询问;如果回答是“是”则询问第二次“是不是第二种颜色的球球”。

 

对于样例2:

最佳策略下:第一次询问“是不是第一种颜色或第二种颜色球球中的一个”,如果回答“是”则询问第二次“是不是第一种颜色的球球”,根据回答可以得出球球的颜色;如果第一次询问回答“否”,则第二次询问“是不是第三种颜色的球球”,同理根据第二次回答也可以唯一得出球球的颜色。

一些废话:

本来让我出校赛的题目的话,是不想出一些乱七八糟的数据结构与算法的。像哈夫曼树这样的数据结构基本不会出现在小小的校赛里。但有时候你不得不相信这种叫做“缘分”的东西。

这个题目的想法实际上不是我自己想出来了的。事情的经过是这样的:

寒假里听另外一个学习AI方面技术的同学闲扯机器学习深度学习什么的东西,然后遇到的一个问题就是“猜球球”。那个同学说用神经网络*&%%&*+(省略一堆听不懂的话)什么的来解决这个问题。然后机智的我突然感觉到:这种类型的问题应该是有解的,也就是有一个确定的算法可以准确的得到最优解,而不是靠AI什么的得到近似最优解。

然后我考虑良久,经过跟出题组其他成员讨论,最终这个题就出现在了校赛上。

 

校赛的时候没人做出来这个题,我想了一下大概有以下原因:

1.思路确实有点绕

2.题面太长太复杂,导致大家一看就头晕

3.前面的题花费了太多时间,轮到了这个题没有时间和精力去做

4.出题者在完全放松没有压力的情况下思考、论证题解都想了几个小时,赛场上可能真的很难。(而且经过出题组的讨论,这个题被定义为【难】的等级,是本次比赛除去压轴题和失效题以外最难的题)

题目思路:

先贴出来讲题的PPT上的解题思路:

因为任意一次询问和回答,都可以确定其中一半的球球集合包含目标球,另一半则不包含目标球。然后再对包含目标球的球球集合进一步划分,直到包含目标球的集合里只包含一个球,就可以百分百确定了。这样就得到了一个决策树(二叉形状),二叉决策树根节点到每个叶子的路到都是期中一种情况的解决方案,显然深度就是询问次数。       则有:期望=∑(询问次数*每种情况出现的概率)=∑(叶子对应的深度*它出现在盒子里的概率)。        而我们知道:这个公式   ∑(深度*元素出现的概率 )    与某种编码方案的编码长度期望公式 相同。询问次数的期望最小也就是编码长度的期望最小。而解决这个问题的经典方法就是——哈夫曼树

然后我稍稍再解释一遍:

由题意可知,tongtong的目的是确定箱子里的球球的颜色到底是什么。

对于策略问题,我们建立一颗“决策树”。树上每个节点代表当前状态,也就是“候选球”的集合。目的是寻找“目标球”

一开始,集合中的元素是全体球球。表示所有的球都有可能是箱子里的球球(当然要除去出现概率为0的球,这是一个出题者自己都踩进去的坑点)。

然后我们要开始询问。这一次询问会将集合中的元素分成两半,也就是两个子集,假设这两个子集分别为A和B,我们询问的格式等价为:“箱子里的球球是集合A中的一个?”,我们知道,不管回答是“正确”还是“错误”,我们都可以确定“目标球球”到底在哪一半中。(就像你询问“你是不是男生”这种答案是二选一的问题,只要对方是"诚实的"不管对方的答案是什么,你都可以确定正确答案)

也就是说:每次询问都会将当前的集合分成两部分。而回答的“正确”和“错误”会导致两种互斥的状态转移。这也就是决策树的分叉。直到我们得到一个只包含一个元素的集合,就可以百分百确定“目标球”到底是什么了。

现在我们要求的就是怎样询问(怎样决策)可以使平均询问次数最少(期望最小)。

在决策树上,每个叶子都代表一种球球颜色可能的情况,每种情况出现的概率就是球球出现在箱子里的概率,询问次数显然就是叶子所在的深度。

我们通过对比:本题的期望公式  和  哈夫曼编码求某种编码方案的平均编码长度(长度的期望)公式,发现两个公式是完全一样的(同构的),都是 

期望 = ∑(深度*元素出现的概率 )

所以我们可以将题目转化为:

每个球球都是一个字符,每个字符出现的使用不同。给出所有字符使用频率,请给出一个前缀码的编码方案,使平均码长最短

然后学过《数据结构》的大二同学应该知道,最佳编码就是“哈夫曼编码”,求该方案的算法就是构造“哈夫曼树”。

 时间复杂度:

构造哈夫曼树的经典算法的时间复杂度为O(n*n),使用优先队列(堆)优化后只需要O(nlogn)。

为了降低难度,所以比赛设定O(n*n)就可以判对了。

#include
#include
using namespace std;
int const maxn=2e3+5;
double co[maxn];
struct node
{
    int i;
    double v;
    struct node*lc,*rc,*self;
    node()
    {
        lc=rc=NULL;
        self=this;
    }
};
node mem[maxn<<2];
int m=0;
node *alloc()
{

    return &mem[m++];
}
node *lst[maxn];
bool operator<(node a,node b)
{
    return a.vlc==NULL && rt->rc==NULL)
        return d*rt->v;
    double ret=0;
    ret+=dfs(rt->lc,d+1);
	ret+=dfs(rt->rc,d+1);
	return ret;
}
int main()
{
	double sum=0;
    int n;
    cin>>n;
    double t;
    for(int i=1;i<=n;i++)
    {
        cin>>t;
        if(t==0)
		{
			n--;
			i--;
			continue;
		 }
		sum+=t;
        lst[i]=alloc();
        lst[i]->v=t;
        lst[i]->i=i;
    }
    priority_queueque;
    while(que.empty()==0)
        que.pop();
    for(int i=0;i2)
            		que.pop();
			}
        }
        node a=que.top();
        que.pop();
        node b=que.top();
        que.pop();
        node *rt=alloc();
        rt->i=a.i;
        rt->v=a.v+b.v;
        rt->lc=a.self;
        rt->rc=b.self;
        lst[b.i]=NULL;
        lst[a.i]=rt;
    }
    int root;

    for(root=1;root<=n;root++)
        if(lst[root])
            break;
    double ans=dfs(lst[root],0);
    printf("%.7f\n",ans);

}

 

你可能感兴趣的:(ACM)