noip2016换教室

题目传送门

一道困扰了我很久的题,好多次的刷题计划里都自动略过这道,一部分原因是因为这题目名字和noip2012借教室实在过于相像,另一部分原因,是这道题看起来实在太复杂了......

noipCSP迫在眉睫,于是我开始做这题,做着做着,感觉海星,有点上头(x

不说了,来看题吧


1.题目解析

题目大意是,现在有一张图,牛牛每个时间段会从给定的一个点走到另一个点,每个时间段内他可以选择申请改去另一个点,但这种申请只能进行至多m次。每次申请有失败的可能,并且申请失败依旧会算入总数中。已知每次申请成功的概率,求期望走到的最短路。

首先,我们要解决一个问题,什么是期望?期望又可以称为带权平均数,具体可以查看百度百科(这里)。它和求概率的最大差别就是期望在求值的时候会算上权值。可能有点不清楚,让我举个栗子解释一下。

在这道题中,假设现在的选择是从i走到j,申请改变到x,那么这段路的期望值就是a[i][x]*p[i](当前最短路乘以申请成功的概率)+a[i][j]*(1-p[i])(申请不成功的情况),这里p[i]的求值会在后文里提到。

明白了吗,接下来,我们就要开始思考该如何解题了


 2.一些处理

我们首先要求出几个点之间的最短路,由于数据量并不是很大,在所有方法里随便选一个就好,我直接打了最简单的 floyed。因为要多次取用,建议直接使用邻接矩阵(其实就是懒)。当然如果你想用 Dijkstra 或者别的算法的话,那也可以,随便...... 

这部分直接粘板子上去就可以。

void floyed()
{
    for(int k=1;k<=v;k++)
        for(int i=1;i<=v;i++)
            for(int j=1;j<i;j++)
                if(f[i][k]+f[k][j]<f[i][j])
                {
                      f[i][j]=f[i][k]+f[k][j];
                      f[j][i]=f[i][k]+f[k][j];
                }   
}

记得要先给 f 数组赋好极大值,否则在后来进行决策的时候会被判断成可以通过(因为中间是取最小值来着)

 

然后是 dp 方程的状态。

首先我们需要用 dp 方程记录当前时间点和换教室的次数,于是先设置两维 i 和 j 代表当前这次转移是 i 时间点时已经申请了 j 次交换。又由于每次进行转移的时候,都有申请更换和不申请更换两种选择,所以咱们再添加一维 k 代表此时是否申请了更换。

所以最后的 dp 数组就是 dp[i][j][k] 代表 i 时间时已经更换了 j 次教室并且当前的更换状态是 k(0或1)。

下面让我们来推导 dp 的转移方程。


3. dp 方程推导

这道题里最麻烦的部分就是 dp 方程。各种状态真的写疯我......

来分成几个部分看一下。

对于每一个点,我们都可以枚举当前换了 j 次教室的情况。所以两重枚举分别如下:

for(int i=2;i<=n;i++)
    {
        for(int j=0;j<=min(i,m);j++)//当 i 小于 m 的时候,最多更换 i 次,所以可以进行剪枝
        {
                
        }
    }

 对于每个dp状态,都有换和不换两种情况。

如果不换的话,那么当前的最短路径就是上一次更换或不更换的最小值。上一次换,这次便应该是从 d[i-1] 走到 c[i]。上一次不换就会麻烦一点,需要计算申请成功和不成功的期望和,方程用语言描述比较麻烦,直接看具体转移。

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

 如果换,那考虑的就是四种情况。

换成功,上一次换了

换失败,上一次换了

换成功,上一次没换

换失败,上一次没换

接下来迎来本代码最长的一句话

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

很 不 友 好

 

然后因为换教室的次数只要小于给定次数就可以满足,所以我们给所有可能取一个最小值,即可得出答案。


 

代码

#include<bits/stdc++.h>
using namespace std;
int c[2005],d[2005];
double k[2005],dp[2005][2005][2],f[2005][2005];
int n,m,v,e;
void floyed()
{
    for(int k=1;k<=v;k++)
        for(int i=1;i<=v;i++)
            for(int j=1;j<i;j++)
                if(f[i][k]+f[k][j]<f[i][j])
                {
                      f[i][j]=f[i][k]+f[k][j];
                      f[j][i]=f[i][k]+f[k][j];
                }   
}
int main()
{
//    freopen("P1850_18.in","r",stdin);
//    freopen("P1850.out","w",stdout);
    cin>>n>>m>>v>>e;
    for(int i=1;i<=n;i++) cin>>c[i];
    for(int i=1;i<=n;i++) cin>>d[i];
    for(int i=1;i<=n;i++) cin>>k[i];
    
    for(int i=1;i<=v;i++)
        for(int j=1;j<i;j++)
            f[i][j]=f[j][i]=2147483640;
            
    for(int i=1;i<=e;i++) 
    {
        int x,y;
        double z;
        cin>>x>>y>>z;
        f[x][y]=min(f[x][y],z);
        f[y][x]=min(f[y][x],z);
    }
    
    floyed();
    
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            dp[i][j][0]=2147483640;
            dp[i][j][1]=2147483640;
        }
    }
    dp[1][0][0]=0;dp[1][1][1]=0;
    
    for(int i=2;i<=n;i++)
    {
        //double road=f[c[i-1]][c[i]];
        for(int j=0;j<=min(i,m);j++)
        {
            dp[i][j][0]=min(dp[i-1][j][0]+f[c[i-1]][c[i]],dp[i-1][j][1]+f[d[i-1]][c[i]]*k[i-1]+f[c[i-1]][c[i]]*(1-k[i-1]));
            if(j!=0) dp[i][j][1]=min(dp[i-1][j-1][0]+f[c[i-1]][d[i]]*k[i]+f[c[i-1]][c[i]]*(1-k[i]),dp[i-1][j-1][1]+f[d[i-1]][d[i]]*k[i]*k[i-1]+f[d[i-1]][c[i]]*(1-k[i])*k[i-1]+f[c[i-1]][d[i]]*(1-k[i-1])*k[i]+f[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i]));    
        }
    } 
    double minn=2147483640;
    for(int i=0;i<=m;i++)
    {
        minn=min(dp[n][i][0],min(dp[n][i][1],minn));
    }
    printf("%.2f",minn);
    return 0;
}

 

posted @ 2020-12-19 12:03  枣子*1  阅读(68)  评论(0编辑  收藏  举报