• [NOIP2016]换教室(概率期望$DP$)


    其实吧我老早就把这题切了……因为说实话,这道题确实不难啊……李云龙:比他娘的状压DP简单多了

    今天我翻以前在Luogu上写的题解时,突然发现放错代码了,然后被一堆人(hack)……蓝瘦啊(ORZ)

    嗯,还是有些点需要注意以下的!以下是今年4月写的:


    (mathcal{color{red}{Description}})

    对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程。

    在可以选择的课程中,有 (2n)节课程安排在 (n)个时间段上。在第$ i $((1 leq i leq n))个时间段上,两节内容相同的课程同时在不同的地点进行,其中,牛牛预先被安排在教室(c_i)上课,而另一节课程在教室$ d_i$ 进行。

    在不提交任何申请的情况下,学生们需要按时间段的顺序依次完成所有的 (n) 节安排好的课程。如果学生想更换第(i) 节课程的教室,则需要提出申请。若申请通过,学生就可以在第 i 个时间段去教室$ d_i$上课,否则仍然在教室 (c_i)上课。

    由于更换教室的需求太多,申请不一定能获得通过。通过计算,牛牛发现申请更换第 (i) 节课程的教室时,申请被通过的概率是一个已知的实数 (k_i),并且对于不同课程的申请,被通过的概率是互相独立的。

    学校规定,所有的申请只能在学期开始前一次性提交,并且每个人只能选择至多 (m)节课程进行申请。这意味着牛牛必须一次性决定是否申请更换每节课的教室,而不能根据某些课程的申请结果来决定其他课程是否申请;牛牛可以申请自己最希望更换教室的(m) 门课程,也可以不用完这 $m $个申请的机会,甚至可以一门课程都不申请。

    因为不同的课程可能会被安排在不同的教室进行,所以牛牛需要利用课间时间从一间教室赶到另一间教室。

    牛牛所在的大学有(v)个教室,有(e)条道路。每条道路连接两间教室,并且是可以双向通行的。由于道路的长度和拥堵程度不同,通过不同的道路耗费的体力可能会有所不同。 当第 (i)(1 leq i leq n-1))节课结束后,牛牛就会从这节课的教室出发,选择一条耗费体力最少的路径前往下一节课的教室。

    现在牛牛想知道,申请哪几门课程可以使他因在教室间移动耗费的体力值的总和的期望值最小,请你帮他求出这个最小值。

    (mathcal{color{red}{Solution}})

    那么对于这道题而言,先捋清楚题目是求什么的吧:

    对于这个无向连通图,我们将每走一步定义为一个阶段。那么每一个阶段都有两种可能性:(p_i)的概率去(d_i),但是在所有的(d[i])((1<=i<=n))至多可以走(m)个,((1-p_i))的概率去(c[i])。而我们要求的,就是在这(n)个阶段结束之后的路程最小期望。

    那么其实状态之间的转移,我们不难看出有两种状态的转移:从(d[i-1])或从(c[i-1])转移过来。而因为实际上对于这个(DP)而言,因为数据不大,所以不需要优化什么的(qwq),记录每种状态是可行的。

    那么很显然啊,我们首先要预处理出每两个点之间的最短路来,方便状态的转移。而在这里,最简单的就是(Floyd)(qwq)((n^3)显然可以接受())

        for(qwq int k=1;k<=v;k++)
          for(qwq int i=1;i<=v;i++)
            for(qwq int j=1;j<i;j++)
              if(f[i][k]+f[k][j]<f[i][j])
                 f[i][j]=f[j][i]=f[i][k]+f[k][j];
    

    然后就是(DP)方程了:

    我们定义(dp[i][j][0/1])来表示当前为第(i)个阶段,连同这一次已经用了(j)次换教室的机会,当前这次换((1))不换((0))的最小期望路程总和。

    那么转移就可以如此转移:

    这次不换:
    (dp[i][j][0]=) (min()上次不换的(dp+)这两次之间的路程 (~~)(~~)上次概率换了之后的(dp+p[i] imes)上次换了的教室与这次不换的教室之间的距离(+(1-p[i]) imes)上次不换的教室与这次不换的教室之间的距离())

    “诶,为什么上次概率换了之后(即逗号之后的一大串)要加两个期望啊?”

    这个问题就是(rqy)大佬给我解决的,现在我要农夫山泉一把了:因为在上一次换教室时是“概率”交换,所以不一定会换呀。所以要把两种情况的都加上(qwq)

    到这儿我们就可以发现,其实换教室比不换教室是要多一重状态的,因为换教室总要牵扯“概率成功”的问题(qwq)

    那其实接下来的状态转移方程就很简单了:一次换一次不换,遇到不换就((1-p[i])),遇到换就(p[i]);两次都不换就不用枚举概率。而且由于牵扯到两次都是概率性事件(比如两次都换)之类的,这个时候需要的就是乘法原理了。

    那么(DP)即如下:

    #define qwq register 
    
        for(qwq int i=1;i<=n;i++)
         for(qwq int j=0;j<=m;j++)
           dp[i][j][0]=dp[i][j][1]=999999999;
         
        dp[1][0][0]=dp[1][1][1]=0;
        for(qwq int i=2;i<=n;i++){
          double dist1=f[c[i-1]][c[i]],dist2=f[d[i-1]][c[i]],dist3=f[c[i-1]][d[i]];
          for(qwq int j=0;j<=min(m,i);j++)
           {                     
              dp[i][j][0]=min(dp[i-1][j][0]+dist1,dp[i-1][j][1]+dist2*p[i-1]+dist1*(1-p[i-1]));
              if(j!=0)
              dp[i][j][1]=min(dp[i-1][j-1][0]+dist3*p[i]+dist1*(1-p[i]),dp[i-1][j-1][1]+dist1*(1-p[i-1])*(1-p[i])+dist3*(1-p[i-1])*p[i]+dist2*(1-p[i])*p[i-1]+dist2*p[i-1]*p[i]);
           }   
    	}          
    

    总结:遇到期望的题目时一定要全面考虑啊!我们可以发现这个题的(dp)方程其实并不难想。

    完结撒花!

    //感谢rqy大佬qwqqq 
    #include<iostream>
    #include<cstdio>
    #define qwq register 
    using namespace std;
    double p[10001],f[2001][2001],dp[2001][2001][2];
    int a[2001][2001],c[20001],d[20001];
    inline double min(double a,double b){
        return a<b?a:b;
    }
    inline int  qread(){
        int  k = 0;
        char c;
        c = getchar();
        while(!isdigit(c))c = getchar();
     	while(isdigit(c)){
            k = (k<<1)+(k<<3)+c-48;
        	c = getchar();
        }
        return k ;
    }
    inline double qread_double()
    {
        double k=0;char c=getchar();
        while(!isdigit(c))c=getchar();
        while(isdigit(c))k=k*10+(c-48),c=getchar();
        if(c=='.')
        {
            double base=0.1;c=getchar();
            while(isdigit(c))k=k+(c-48)*base,base/10,c=getchar();
        }
        return k;
    }
    int main()
    {
        int n,m,v,e,a1,b1,c1;
    	cin>>n>>m>>v>>e;
        for(qwq int i=1;i<=n;i++)c[i]=qread();
        for(qwq int i=1;i<=n;i++)d[i]=qread();
        for(qwq int i=1;i<=n;i++)p[i]=qread_double();
        
    	for(qwq int i=1;i<=v;i++)
         for(qwq int j=1;j<i;j++)
          f[i][j]=f[j][i]=999999999;
       
        for(qwq int i=1;i<=e;i++){
        	a1=qread(),b1=qread(),c1=qread();
            f[a1][b1]=f[b1][a1]=min(f[a1][b1],c1);
        }
        
        for(qwq int k=1;k<=v;k++)
          for(qwq int i=1;i<=v;i++)
            for(qwq int j=1;j<i;j++)
              if(f[i][k]+f[k][j]<f[i][j])
                 f[i][j]=f[j][i]=f[i][k]+f[k][j];
                 
        for(qwq int i=1;i<=n;i++)
            for(qwq int j=0;j<=m;j++)
                dp[i][j][0]=dp[i][j][1]=999999999;
         
        dp[1][0][0]=dp[1][1][1]=0;
        for(qwq int i=2;i<=n;i++){
         double add1=f[c[i-1]][c[i]];
          for(qwq int j=0;j<=min(m,i);j++)
           {                     
              dp[i][j][0]=min(dp[i-1][j][0]+add1,dp[i-1][j][1]+f[d[i-1]][c[i]]*p[i-1]+f[c[i-1]][c[i]]*(1-p[i-1]));
              if(j!=0)
              dp[i][j][1]=min(dp[i-1][j-1][0]+f[c[i-1]][d[i]]*p[i]+f[c[i-1]][c[i]]*(1-p[i]),dp[i-1][j-1][1]+f[c[i-1]][c[i]]*(1-p[i-1])*(1-p[i])+f[c[i-1]][d[i]]*(1-p[i-1])*p[i]+f[d[i-1]][c[i]]*(1-p[i])*p[i-1]+f[d[i-1]][d[i]]*p[i-1]*p[i]);
           }   
    	}          
    	                           
        double hahaha=9999999999;
        for(int i=0;i<=m;i++){
        hahaha=min(dp[n][i][0],min(dp[n][i][1],hahaha));}
        printf("%.2lf",hahaha);
    }
    

    (By) (Flower) _ (pks)

  • 相关阅读:
    位集合
    多线程进行http请求
    mysql--测试前缀索引能否用于order by 或者 group by
    用mysql触发器实现log记录
    源码安装mysql
    C语言:void指针
    C语言:枚举类型
    C语言:结构体与数组
    C语言:联合变量
    Linux 基础入门
  • 原文地址:https://www.cnblogs.com/pks-t/p/9357672.html
Copyright © 2020-2023  润新知