• 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
    ",ans);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    【机器学习】机器学习12个关键经验教训
    【机器学习】24个终极项目提升您的机器学习知识和技能
    2018-12-21-WPF-弹出-popup-里面的-TextBox-无法输入汉字
    2019-10-31-C#-dotnet-获取整个局域网的-ip-地址
    2018-11-26-win10-uwp-获取窗口的坐标和宽度高度
    2019-5-21-dotnet-core-使用-CoreRT-将程序编译为-Native-程序
    2019-5-21-Roslyn-使用-Directory.Build.props-管理多个项目配置
    2019-2-26-SublimeText-快速打开当前文件的文件夹
    2019-2-18-VisualStudio-给项目添加特殊的-Nuget-的链接
    2019-8-31-dotnet-如何在-Mock-模拟-Func-判断调用次数
  • 原文地址:https://www.cnblogs.com/Robert-Yuan/p/5234081.html
Copyright © 2020-2023  润新知