[LnOI2019]加特林轮盘赌(DP,概率期望)

[LnOI2019]加特林轮盘赌(DP,概率期望)

题目链接

题解:

首先特判掉\(p=0/1\)的情况...

先考虑如果\(k=1\)怎么做到\(n^2\)的时间复杂度
\(f[i]\)表示有\(i\)个人,\(k=1\)的时候幸存的概率
\(g[i][j]\)表示\(i\)个人每个人挨一下恰好死\(j\)个人的概率
我们就可以列出转移方程:

\[f[i]=(1-p)\sum_{j=1}^{i-1}{f[j]*g[i-j]}+f[i]*g[i][0] \]

  • 含义:枚举打了一圈后剩下多少人,乘\(1-p\)是因为要保证自己不死

把含有\(f[i]\)的项全部移到左边,化简得:

\[f[i]=\frac{(1-p)\sum_{j=1}^{i-1}{f[j]*g[i-1][i-j]}}{1-g[i][0]} \]

于是我们就可以愉快地\(dp\)
数组\(g\)存不下来,\(dp\)的时候动态更新就好啦

如果\(k!=1\)怎么办呢?
我们可以把\(1\sim k-1\)的这些人先打一遍,对于每种剩下的人数分别计算答案,然后按照概率加起来就好了

\(double\)运算常数很大,比赛时一直\(TLE\)\(65\)分,关于这个常数的问题有两种解决方法:

  • 可以把所有数全部乘上一个\(2e9\)转化为\(long long\)进行运算,算完在除回来,缺点是精度较低,容易写错
  • 直接把\(double\)换成\(longdouble\)就行了,不过稍微慢点

看了看排行榜,好像有\(O(n)\)的做法?

代码:

#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define qmax(x,y) (x=max(x,y))
#define qmin(x,y) (x=min(x,y))
#define mp(x,y) make_pair(x,y)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
inline int read(){
	int ans=0,fh=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
		ans=ans*10+ch-'0',ch=getchar();
	return ans*fh;
}
const ll maxn=1e4+100,w=2e9;
int n,k;
ll p,g[maxn],f[maxn],pp[maxn];
double php;
int main(){
//	freopen("nh.in","r",stdin);
//	freopen("zhy.out","w",stdout);
	scanf("%lf%d%d",&php,&n,&k);
	if(php==0){
		printf(n==1?"1\n":"0\n");
		return 0;
	}
	p=php*w;
	int x=k-1;
	f[1]=1*w,g[0]=w-p,g[1]=p;
	if(x==1)
		for(int j=0;j<=n;j++)
			pp[j]=g[j];
	for(int i=2;i<=n;i++){
		int j;
		for(j=1;j<i-3;j+=4){
			f[i]+=f[j]*g[i-j]/w;
			f[i]+=f[j+1]*g[i-j-1]/w;
			f[i]+=f[j+2]*g[i-j-2]/w;
			f[i]+=f[j+3]*g[i-j-3]/w;
		}
		for(;j<i;j++) f[i]+=f[j]*g[i-j]/w;
		(f[i]*=w-p)/=w;
		for(j=i;j>=4;j-=4){
			g[j]=(g[j-1]*p+g[j]*(w-p))/w;
			g[j-1]=(g[j-2]*p+g[j-1]*(w-p))/w;
			g[j-2]=(g[j-3]*p+g[j-2]*(w-p))/w;
			g[j-3]=(g[j-4]*p+g[j-3]*(w-p))/w;
		}
		for(;j;j--) g[j]=(g[j-1]*p+g[j]*(w-p))/w;
		(g[0]*=w-p)/=w;
		if(i==x)
			for(int j=0;j<=n;j++)
				pp[j]=g[j];
		(f[i]*=w)/=w-g[0];
	}
	if(x==0){
		printf("%.12lf\n",f[n]*1.0/w);
		return 0;
	}
	double ans=0;
	for(int i=0;i<=x;i++){
		ans+=pp[i]*f[n-i]/w;
	}
	printf("%.12lf\n",ans*1.0/w);
	return 0;
}
posted @ 2019-03-10 17:23  nianheng  阅读(411)  评论(0编辑  收藏  举报