P1850 换教室

原题链接  https://www.luogu.org/problem/P1850

这是一道期望 dp 的题目,其中还有不少坑,AC 的道路上充满坎坷啊~

简化题意

有 v 个点 e 条边,需要在其中某些点里上课,并有几率换到另外别的点上去,但是只要 m 次机会可以换(只是可以,不一定成功),问最小的期望花费;

解题思路

嗯,依然先按照一般 dp 的思路走起,要上 n 堂课可以申请 m 次,那么我们就设 dp [ i ][ j ] 表示前 i 堂课申请了 j 次的最小期望花费;

发现好像没法转移,因为我们只知道前几次有没有申请,并不知道这一次是否申请,换句话说我们不知道 i-1 和 i 用的是哪一间教室,那么怎么办?

再加一个维度不就好了~ 来表示第 i 堂课是否申请 。

状态设置:dp [ i ][ j ][ 0/1 ] 表示前 i 堂课申请了 j 次,第 i 堂课有没有申请(0 表示没有申请,1 表示申请了);

状态转移:

把状态设出来后,再转移就很轻松了;

就找第 i 堂课申请和不申请的情况转移一下就好了;

 

一 . 如果第 i 堂课不申请:

那么我们就确定了第 i 堂课会在第 c [ i ] 个教室上课;

1. 第 i-1 堂课也不申请:

我们也能确定第 i-1 堂课在第 c [ i-1 ] 个教室上课,那么这两堂课之间的期望花费是:f [ c [ i-1 ] ][ c [ i ] ]  

2. 第 i-1 堂课申请:

申请是申请了,成不成功谁知道呢,所以我们要考虑全部情况:

有 k [ i-1 ] 的几率成功,那么就会换到 d [ i-1 ] 这间教室去,那么这两堂课之间的期望花费是:k [ i-1 ] * f [ d [ i-1 ] ][ c [ i ]

有(1 - k [ i-1 ])的几率不成功,那么还是会到 c [ i-1 ] 这间教室去上课,那么这两堂课之间的期望花费是:( 1 - k [ i-1 ] ) * f [ c [ i-1 ] ][ c [ i ] ] 

那么第 i-1 堂课申请的总期望花费就是:k [ i-1 ] * f [ d [ i-1 ] ][ c [ i ] ] + ( 1 - k [ i-1 ] ) * f [ c [ i-1 ] ][ c [ i ] ] 

我们在这两种情况取 min 就可以了:

dp [ i ][ j ][ 0 ] = min ( dp [ i-1 ][ j ][ 0 ] + f [ c [ i-1 ] ][ c [ i ] ] , dp [ i-1 ][ j ][ 1 ] + k [ i-1 ] * f [ d [ i-1 ] ][ c [ i ] ] + ( 1 - k [ i-1 ] ) * f [ c [ i-1 ] ][ c [ i ] ] )

 

二 . 如果第 i 堂课申请:

有 k [ i ] 的几率申请成功,在 d [ i ] 间教室上课;

当然有 ( 1 - k [ i ] ) 的几率申请失败,在 c [ i ] 间教室上课;

1. 第 i 间教室不申请:

我们能确定第 i-1 堂课在第 c [ i ] 间教室上,如果第 i 间教室申请成功,那么期望花费:k [ i ] * f [ c [ i-1 ] ][ d [ i ] ] 

如果第 i 间教室申请失败,那么期望花费是:( 1 - k [ i ] ) * f [ c [ i-1 ] ][ c [ i ] ]

那么这种情况的总期望花费就是:k [ i ] * f [ c [ i-1 ] ][ d [ i ] ] + ( 1 - k [ i ] ) * f [ c [ i-1 ] ][ c [ i ] ]

2. 第 i 间教室申请:

这种情况就有些麻烦了(倒吸一口凉气~不慌)

(1) 两间教室都申请成功:k [ i-1 ] * k [ i ] * f [ d [ i-1 ] ][ d [ i ] ]

(2) 第 i-1 间申请成功,第 i 间申请失败:k [ i-1 ] * ( 1 - k [ i ] ) * f [ d [ i-1 ] ][ c [ i ] ]

(3) 第 i-1 间申请失败,第 i 间申请成功:( 1 - k [ i-1 ] ) * k [ i ] * f [ c [ i-1 ] ][ d [ i ] ]

(4) 两间教室都申请失败:( 1 - k [ i-1 ] ) * ( 1 - k [ i ] ) * f [ c [ i-1 ] ][ c [ i ] ]

其实很好想嘛,只是有点长而已啦~

我们在这两种情况取个 min 就可以了:

dp [ i ][ j ][ 1 ] = min ( dp [ i-1 ][ j-1 ][ 0 ] + k [ i ] * f [ c [ i-1 ] ][ d [ i ] ] + ( 1 - k [ i ] ) * f [ c [ i-1 ] ][ c [ i ] ]dp [ i-1 ][ j-1 ][ 1 ] + k [ i-1 ] * k [ i ] * f [ d [ i-1 ] ][ d [ i ] ]k [ i-1 ] * ( 1 - k [ i ] ) * f [ d [ i-1 ] ][ c [ i ] ]( 1 - k [ i-1 ] ) * k [ i ] * f [ c [ i-1 ] ][ d [ i ] ] + ( 1 - k [ i-1 ] ) * ( 1 - k [ i ] ) * f [ c [ i-1 ] ][ c [ i ] ] )

 

边界设置

到第一个教室是不需要花费的,所以无论第一间教室申请没申请,成功不成功,期望花费都是 0:

dp [ 1 ][ 0 ][ 0 ] = 0;(没有申请)

dp [ 1 ][ 1 ][ 1 ] = 0;(申请了)

 

答案

一层循环从 0~m 枚举一下申请了几次,取最小的作为答案即可。

 

于是我们就可以愉快的 AC 啦~

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
using namespace std;
int read()
{
    char ch=getchar();
    int a=0,x=1;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') x=-x;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        a=(a<<1)+(a<<3)+(ch-'0');
        ch=getchar();
    }
    return a*x;
}
int n,m,v,e,x,y,w;
int c[2001],d[2001],f[2001][2001];
double k[2001],dp[2001][2001][2];
int main()
{
    n=read();m=read();v=read();e=read();  //n节课,m次申请机会,v间教室,e条边 
    for(int i=1;i<=n;i++) c[i]=read();    //如果不申请或申请失败所要去的教室 
    for(int i=1;i<=n;i++) d[i]=read();    //如果申请成功所要去的教室 
    for(int i=1;i<=n;i++) scanf("%lf",&k[i]); //每节课申请成功的概率 
    for(int i=1;i<=v;i++)                 //邻接矩阵存边初始化 
        for(int j=1;j<=v;j++)
            f[i][j]=1e9;
    for(int i=1;i<=v;i++) f[i][i]=0;      
    for(int i=1;i<=n;i++)                 //dp数组初始化,求最小值赋值成无穷大 
        for(int j=0;j<=m;j++)
            dp[i][j][0]=dp[i][j][1]=1e9;
    dp[1][0][0]=0;                        //边界条件 
    dp[1][1][1]=0;
    for(int i=1;i<=e;i++)
    {
        x=read();y=read();w=read();
        f[x][y]=f[y][x]=min(f[x][y],w);   //注意重边的情况 
    }
    for(int k=1;k<=v;k++)                 //Floyd求任意两个教室之间的最短路 
        for(int i=1;i<=v;i++)
            for(int j=1;j<=v;j++)
                f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
    for(int i=2;i<=n;i++)                 //从2开始即可 
    {
        dp[i][0][0]=dp[i-1][0][0]+f[c[i-1]][c[i]];  //一次都不申请的情况一定要单独列出来 
        for(int j=1;j<=m;j++)             //上面一次都不申请的情况列出来了,所以j可以从1开始枚举了,就是为了怕j-1出现负数 
        {                                 //冗长的转移方程 
            dp[i][j][0]=min(dp[i-1][j][0]+f[c[i-1]][c[i]],dp[i-1][j][1]+k[i-1]*f[d[i-1]][c[i]]+(1-k[i-1])*f[c[i-1]][c[i]]);
            dp[i][j][1]=min(dp[i-1][j-1][0]+k[i]*f[c[i-1]][d[i]]+(1-k[i])*f[c[i-1]][c[i]],dp[i-1][j-1][1]+k[i-1]*k[i]*f[d[i-1]][d[i]]+(1-k[i-1])*k[i]*f[c[i-1]][d[i]]+k[i-1]*(1-k[i])*f[d[i-1]][c[i]]+(1-k[i-1])*(1-k[i])*f[c[i-1]][c[i]]);
        }
    }
    double ans=1e9;                        
    for(int i=0;i<=m;i++) ans=min(ans,min(dp[n][i][0],dp[n][i][1])); //取最后的答案,注意i从0开始 
    printf("%.2lf",ans);
    return 0;
}

 

posted @ 2019-08-21 20:59  暗い之殇  阅读(265)  评论(0编辑  收藏  举报