BZOJ4008 [HNOI2015]亚瑟王
AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4008
最近在刷HNOI的题。2015day1t1立刻卡壳...果然想不出啊...
看别人的题解,感觉写得总是让人思考好一会儿,于是想写一篇自己大概看得懂的题解。但是这样我就不知道自己的感受到底是不是正确的了,希望有大神指正。
这题有两个关键的条件:
1.若一张牌发动了技能,那么将结束此回合。
2.若一张牌发动过技能那么它不能再发动技能。
这条件1导致我们不能很好的直接通过回合来进行DP,条件2使得我们若通过回合来进行DP需要记录的状态会比较复杂。
所以我们舍弃回合制度的DP,而从其它方面考虑。
由条件2我们想到,一张牌最多发动一次技能,我们若能算出每张牌发动技能的概率就能推知答案。而当我们抛开了回合制度后,就只需要考虑经过卡牌的次数就好了,即给卡牌机会。
设F[i][j]表示给第i个元素分配j个机会的概率。
若第i个元素分配到了j个机会,那么有两种情况:
首先若是i要分到j次机会,那么i-1至少也要分到j次机会,所以一定是由F[i-1][]转移来。
若i-1没有用一个机会那么F[i][j]+=F[i-1][j]*(1-p[i-1])^j
若i-1用了一个机会那么F[i][j]+=F[i-1][j+1]*(1-(1-p[i-1])^j)
可是为什么是乘(1-(1-p[i-1])^j)呢?直观来看,是因为两者必定转移一个,所以加起来=1,那么再仔细点分析,发现1-P(没用一次机会)=P(至少用一次机会)。而这题中却要求了只能用一次,式子中如何表现出用一次的呢?因为机会只-1.
但是这样的话,概率分配是不是出现了问题呢?
所以只能这样感受了:
我只管分给了你多少次机会,你可以用很多次,概率可以相加,我可以只管你是不是至少用了一次。但是如果你用了,不管用了多少,我只给你少一个机会上的限制。
或者还能从另一个角度感受,就是如果我拿了很多个,只有我第一次拿的算数,后面拿的不算。这样当我转移的时候,就只算了最开始拿了一个,后面拿的和不拿的概率之和等于1,在我考虑转移到达的下一个时就可以绑在一起考虑,比拟成第i-1张牌已经被移除了。
至此,差不多就感受完了这道题状态的巧妙之处。
#include<cmath> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; struct card{double p,d;}a[231]; int kase,tj,n,m; double ans,d[231]; double f[231][231],p[231][231]; int main(){ #ifndef ONLINE_JUDGE freopen("arthur.in","r",stdin); freopen("arthur.out","w",stdout); #endif scanf("%d",&kase); while(kase--){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%lf%lf",&a[i].p,&a[i].d); for(int i=1;i<=n;i++) p[i][0]=1; for(int i=0;i<=m;i++) p[0][i]=1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) p[i][j]=p[i][j-1]*(1-a[i].p); memset(f,0,sizeof(f)); memset(d,0,sizeof(d)); f[0][m]=1; for(int i=1;i<=n;i++) for(int j=m;j>=0;j--){ f[i][j]+=f[i-1][j]*p[i-1][j]+f[i-1][j+1]*(1-p[i-1][j+1]); } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) d[i]+=f[i][j]*(1-p[i][j]); ans=0; for(int i=1;i<=n;i++) ans+=a[i].d*d[i]; printf("%.11lf\n",ans); } return 0; }