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;
}
View Code

 

posted @ 2016-03-02 11:00  诚叙  阅读(826)  评论(0编辑  收藏  举报