百度之星2013年4月27日竞赛题目一 Fir

百度之星2个小时只能做1题已经成定式了....只求1题赚点小分就知足了..orz

  比赛一开始,看到第一题激动了半天,正好这几天在学概率dp,看来RP守恒真不是盖得= =,飞速敲完后,完美过了样例,正自恋着准备交,听到X神说被坑了...仔细一想,当时的想法确实是错误的。幸好还没交!

错误代码1:

思路:ans[i]表示长度为i的串的期望时间,则ans[i]=∑(max(ans[j-1],ans[i-j])+1)*(1/i) [j代表当前爆炸的位置],然后从前往后递推就行了

View Code
#include<stdio.h>
double dp[410];
double max(double x,double y){
    return x>y?x:y;
}

void init(){
    int i,pos;
    dp[0]=0;dp[1]=1;
    for(i=2;i<=400;i++){
        double temp=1.0/i;
        for(pos=1;pos<=i;pos++){
            dp[i]+=(max(dp[pos-1],dp[i-pos])+1)*temp;
        }
    }
}
int main()
{
    int T,n;
    init();
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        printf("%lf\n",dp[n]);
    }
    return 0;
}

从上面可以看到,对于一个长度为n的串,分成a,b两段时,我们直接用a和b的期望来算的,实际上a,b之间再爆炸的时候是相互影响的,也就是不能分别独立出来算然后取最大值。

想到这点之后出现了错误代码2:

思路:dp[i][j]表示炸开以后的两段分别长i,j时,剩下时间的期望,则:

  dp[i][j]=∑(max(dp[k1-1][i-k1],dp[k2-1][j-k2])+1)*p  [k1,k2分别为2段炸开的位置]

  然后长为n的期望时间就是dp[n][0]

View Code
#include<stdio.h>
#include<string.h>

double dp[410][410];    //两段分别为a,b时的期望
double max(double x,double y){
    return x>y?x:y;
}

void init()
{
    int i,j,pos1,pos2;
    memset(dp,0,sizeof(dp));
    dp[0][0]=0;
    dp[0][1]=dp[1][0]=1,dp[1][1]=1;
    for(i=2;i<=400;i++){
        for(j=1;j<=i;j++){
            double temp=1.0/i/j;
            for(pos1=1;pos1<=i;pos1++)
                for(pos2=1;pos2<=j;pos2++)
                    dp[i][j]+=(max(dp[pos1-1][i-pos1],dp[pos2-1][j-pos2])+1)*temp;
                dp[j][i]=dp[i][j];
        }
        double temp=1.0/i;
        for(j=1;j<=i;j++)
            dp[i][0]+=(dp[j-1][i-j]+1)*temp;
        dp[0][i]=dp[i][0];
    }
}

int  main()
{
    int T,n;
    init();
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        printf("%.5lf\n",dp[n][0]);
    }
    return 0;
}

代码2的改进是,考虑到了分成的a,b两个爆炸不是独立的,对每种情况暴力枚举,复杂度大概是O(n^4),出来以后完美过了样例,但是保险起见还是没敢交。

后来听到Y神的想法觉得挺有道理,直接算概率确实很保险,因为上面第二种方法等于还是要用期望,而期望这个东西直接取最大值就会觉得怪怪的,直觉就是这样是错的。

于是就诞生了第三种代码,正误未知,但是思路肯定不会错,因为算概率的话,每种状态的转移都是有理可依的,复杂度O(n^4),只能打表用:

思路:dp[i][j]表示长度为i的串,j次爆炸完的概率,则:

  如果dp[k-1][j-1]存在

    dp[i][j]+=∑dp[k-1][j-1]*dp[i-k][h]*(1.0/i) (h小于等于i-k,小于等于j-1)

  如果dp[i-k][j-1]存在

    dp[i][j]+=∑dp[k-1][h]*dp[i-k][j-1]*(1.0/i) (h小于等于k-1,小于等于j-1)

  这两种都存在,则还要减去重复算的一种情况.

View Code
#include<stdio.h>
#include<string.h>
double dp[410][410];//长度m用n次概率

void init()
{
    int i,j,k,pos,lenth1,lenth2;
    memset(dp,0,sizeof(dp));
    dp[0][0]=1;
    dp[1][0]=dp[0][1]=0;
    dp[1][1]=1;
    for(i=2;i<=400;i++){
        double temp=1.0/i;
        for(j=1;j<=i;j++){    //分的次数
            for(pos=1;pos<=i;pos++){    //爆炸的位置
                lenth1=pos-1;    //前一段长度
                lenth2=i-pos;    //后一段长度
                for(k=0;k<=j-1&&k<=lenth1;k++)
                    dp[i][j]+=dp[lenth2][j-1]*dp[lenth1][k]*temp;
                for(k=0;k<=j-1&&k<=lenth2;k++)
                    dp[i][j]+=dp[lenth1][j-1]*dp[lenth2][k]*temp;
                if(dp[lenth1][j-1]&&dp[lenth2][j-1])                //减掉重复,这一步容易忘!找了好久
                    dp[i][j]-=dp[lenth1][j-1]*dp[lenth2][j-1]*temp;
            }
        }
    }
}
int main()
{
    int T,n;
    freopen("input.txt", "rt", stdin);
    freopen("output.txt", "wt", stdout);
    init();
    /*scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        double ans=0;
        for(int i=1;i<=n;i++)
            ans+=dp[n][i]*i;
        printf("%.5lf\n",ans);
    }*/
    for(int i=1;i<=400;i++)
    {
        double ans=0;
        for(int j=1;j<=i;j++)
            ans+=dp[i][j]*j;
        printf("%.5lf,",ans);
    }
}

这个代码调好以后已经只剩5分钟了...确定n=10和n=400的正确性后...不管三七二十一直接交了..求RP爆发吧

总体来说还是我太弱了,概率dp这个东西,没有确定的递推式还是不要胡乱假设的好~

原文地址:https://www.cnblogs.com/SolarWings/p/3048163.html