CF_229E_Gift_概率DP+组合数学
Posted fcwww
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF_229E_Gift_概率DP+组合数学相关的知识,希望对你有一定的参考价值。
CF_229E_Gift_概率DP+组合数学
题目描述:
很久很久以前,一位老人和他的妻子住在蔚蓝的海边。有一天,这位老人前去捕鱼,他捉到了一条活着的金鱼。鱼说:“噢,老渔人!我祈求你放我回到海里,这样的话我保证给你n样礼物——任何你想要的礼物!”鱼给了老人一张礼物的清单并附上了礼物的价值。清单上的一些礼物可能会有相同的名称、不同的价值,也可能会有不同的名称、相同的价值。然而,清单上不会出现名称和价值都相同的礼物。老人可以向鱼索要清单上的n个礼物。假设清单上有p样礼物价值相同,则该礼物不能被索要超过p次。老人知道,如果他索要同一个名称s次,那么金鱼会等概率地随机选择该名称下的s样礼物。老人想要满足他贪心的妻子,所以他会选择价值最高的n样礼物。此外,如果有不同的方法选择最高价值的n样礼物,他会等概率地采用其中任意一个方法。老人想知道,他能拿到n样价值最高的礼物的概率是多少。然而他不怎么擅长概率论,于是就来向你求助。
输入格式
第一行有两个整数n和m,n代表老人想要的礼物数量、m代表清单上各不相同的名称的数量。接下来有m行,第i行首先包括一个整数ki(ki> 0),代表第i个名称下礼物的数量。接着,ki个各不相同的整数cij(1<=cij<=109)是这些礼物的价格。
输出格式
输出仅一行:一个实数——老人拿到n样价值最高的礼物的概率。绝对误差不超过10-9时,输出被认为是正确的。
样例
gift1.in gift1.out 3 1 3 10 20 30 1.000000000 //////////// gift2.in gift2.out 3 2 1 40 4 10 20 30 40 0.166666667 ///////////// gift3.in gif3.out 3 2 3 1 2 3 1 1 0.666666667
首先能够发现有一些礼物是必须选的,设编号为$i$的礼物中必须选的个数为$cnt[i]$,有一些可能选的。
然后发现必须选的礼物一定是所有礼物中价值第$n$大的,记为$val[n]$,并且可能选的那些一定等于$val[n]$,并记录等于$val[n]$的礼物个数$ry$,应该选价值为$val[n]$的礼物个数$rm$。
题中又说了编号相同则价值不会相同,所以同一编号下最多只会有一个是可能选的。
可以暴力枚举那些编号是选的,然后除上组合数。最后再除以$C[ry][rm]$。
暴力复杂度指数级别,显然过不了这题,于是加上个记忆化。
设$F[i][j]$表示考虑前$i$编号的礼物,已经选了$j$个可以选的最后成功的概率。
那么有初始值$F[0][0]=1$,答案为$F[m][rm]/C[ry][rm]$。
有转移:$F[i][j]=F[i-1][j]/C[k[i]][cnt[i]]$。如果$i$编号中有可能选的,则需要额外加上$F[i-1][j-1]/C[k[i]][cnt[i]+1]$。
时间复杂度$O(nm)$
代码:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; typedef double f3; int n,m,k[1050],val[1050],a[1050][1050],cnt[1050],w[1050],is[1050]; f3 C[1050][1050],f[1030][1030]; inline bool cmp(int x,int y) {return x>y;} int main() { //freopen("gift.in","r",stdin); //freopen("gift.out","w",stdout); scanf("%d%d",&n,&m); int i,j; int tot=0; for(i=1;i<=m;i++) { scanf("%d",&k[i]); for(j=1;j<=k[i];j++) { scanf("%d",&a[i][j]); val[++tot]=a[i][j]; } } int ry=0,rm=0; sort(val+1,val+tot+1,cmp); int v=val[n]; for(i=1;i<=m;i++) { for(j=1;j<=k[i];j++) { if(a[i][j]>v) cnt[i]++; if(a[i][j]==v) { w[++ry]=i; is[i]=1; } } } for(i=n;i>=1;i--) { if(val[i]==val[n]) rm++; else break; } for(i=0;i<=tot;i++) C[i][0]=C[i][i]=1; for(i=1;i<=tot;i++) { for(j=1;j<=i;j++) { C[i][j]=C[i-1][j]+C[i-1][j-1]; } } f3 tmp=C[ry][rm]; //printf("%d\n",tot); //for(i=0;i<=ry;i++) f[0][i]=1; f[0][0]=1; for(i=1;i<=m;i++) { for(j=0;j<=rm;j++) { if(is[i]&&j) { f[i][j]=f[i-1][j-1]/C[k[i]][cnt[i]+1]; } f[i][j]+=f[i-1][j]/C[k[i]][cnt[i]]; //printf("%.9f\n",(double)f[i][j]); } } printf("%.9f\n",f[m][rm]/tmp); }
以上是关于CF_229E_Gift_概率DP+组合数学的主要内容,如果未能解决你的问题,请参考以下文章
CF756D Bacterial Melee[dp,组合数]
CS229笔记一监督学习,线性回归,LMS算法,正态方程,概率解释和局部加权线性回归