CodeForces-229E Gifts

题面:

很久很久以前,一位老人和他的妻子住在蔚蓝的海边。有一天,这位老人前去捕鱼,他捉到了一条活着的金鱼。鱼说:“噢,老渔人!我祈求你放我回到海里,这样的话我保证给你n样礼物——任何你想要的礼物!”鱼给了老人一张礼物的清单并附上了礼物的价值。清单上的一些礼物可能会有相同的名称、不同的价值,也可能会有不同的名称、相同的价值。然而,清单上不会出现名称和价值都相同的礼物。

老人可以向鱼索要清单上的n个礼物。老人知道,如果他索要同一个名称s次,那么金鱼会等概率地随机选择该名称下的s样礼物。老人想要满足他贪心的妻子,所以他会选择价值最高的n样礼物。此外,如果有不同的方法选择最高价值的n样礼物,他会等概率地采用其中任意一个方法。

老人想知道,他能拿到n样价值最高的礼物的概率是多少。然而他不怎么擅长概率论,于是就来向你求助。

1 <= n,m <= 1000输入保证所有ki的总和不超过1000, 并且保证n不大于礼物总数。


题意:

给出m个物品组,现在可以要其中的n个物品;

你的策略是这样的:如果有一种方案,可能得到价值最高的n个物品,那么这种方案就是可取的,最终你会从所有可取的方案中随机取一种;

注意:每个物品只有一个,取这n个物品可以认为是同时的;

答案就是得到价值最高的物品的概率;


题解:

这题的题意有些绕。。。而且精度都吓得我上long double了;

实际上很容易想到的是,先按价值大小排序之后,前面有一些是必拿的;

而后面有一价值相同的东西才是有选择的;

那么对于一个物品组,我们需要关心的只有三种属性;

物品组中物品的总个数size,一定要选的物品数must,可能选的物品数can;

但是题中保证了同一个物品组只有一个相同物品,那么这里的can不是0就是1!

暂且不考虑可能选的物品的影响,答案就是1/ΠC[size i][must i];

组合数要提前处理出来,用个long double的1000*1000数组存;

这时我们其实可以暴搜所有方案,随机数据下速度也挺快,但很容易就被卡掉了;

设可以选的物品共有tot个,要从中选出cnt个;

暴搜的复杂度是C[tot][cnt]的,阶乘的增长速度。。。

所以想想不那么暴力的做法,如果物品组i的可选物品被选了,那么它对答案的贡献就由C[size i][must i]变成了C[size i][must i+1];

这之间差的系数我们很容易得到(可以展开拿出公式,也可以直接用处理好的C数组除);

答案求的是随机取方案的概率,也就是所有方案概率的平均数;

考虑一种方案,它的概率可以表示为1/ΠC[size i][must i]/cnt个不同的系数;

那设f[i][j]表示前i个系数,不同的j个乘在一起的和;

转移显然:f[i][j]=f[i-1][j]+系数*f[i-1][j-1];

然后得到的1/ΠC[size i][must i]/C[tot][cnt]/f[tot][cnt]就是答案了;

复杂度不太好算,但是反正不会超过1000*1000,大概也就是O(m^2)咯;


代码:


#include<iomanip>
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#define N 1100
using namespace std;
typedef long double ld;
int val[N][N],size[N],a[N],tot;
int n,m,tt,cnt,must[N],can[N];
ld C[N][N],fact[N],f[N][N],T[N],ans,mc;
bool cmp(int a,int b)
{
	return a>b;
}
void init()
{
	C[0][0]=1;
	fact[0]=1;
	for(int i=1;i<N;i++)
	{
		fact[i]=fact[i-1]*i;
		C[i][0]=1;
		for(int j=1;j<=i;j++)
		{
			C[i][j]=C[i-1][j]+C[i-1][j-1];
		}
	}
}
void DP()
{
	f[0][0]=1;
	for(int i=1;i<=tt;i++)
	{
		f[i][0]=1;
		for(int j=1;j<=cnt;j++)
		{
			f[i][j]=f[i-1][j]+f[i-1][j-1]*T[i];
		}
	}
}
int main()
{
	int i,j,k;
	scanf("%d%d",&m,&n);
	init();
	for(i=1;i<=n;i++)
	{
		scanf("%d",size+i);
		for(j=1;j<=size[i];j++)
		{
			scanf("%d",&val[i][j]);
			a[++tot]=val[i][j];
		}
		sort(val[i]+1,val[i]+size[i]+1,cmp);
	}
	sort(a+1,a+tot+1,cmp);
	for(i=1,tt=0;i<=n;i++)
	{
		for(j=1;j<=size[i];j++)
		{
			if(val[i][j]>a[m])
				must[i]++;
			else if(val[i][j]==a[m])
			{
				T[++tt]=(ld)(must[i]+1)/(size[i]-must[i]);
				can[i]++;
				break;
			}
			else
				break;
		}
	}
	for(i=m,cnt=0;i>0;i--)
	{
		if(a[m]==a[i])
			cnt++;
		else break;
	}
	for(i=1,mc=1;i<=n;i++)
	{
		mc*=C[size[i]][must[i]];
	}
	DP();
	ans=f[tt][cnt]/C[tt][cnt]/mc;
	cout<<fixed<<setprecision(10)<<ans<<endl;
	return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值