【题解】P1850 [NOIP2016 提高组] 换教室(组合数学,概率,期望)

【题解】P1850 [NOIP2016 提高组] 换教室

大概是我做的第一道概率期望 DP。


题目链接

P1850 [NOIP2016 提高组] 换教室

思路分析

根据题意。

\(dp_{i,j}\) 表示走到了第 \(i\) 个教室,当前换了 \(j\) 次的方案数。

考虑转移。

发现转移不仅仅与 \(i\)\(j\) 有关,还跟一个状态有关:当前这个教室换还是没有换

所以我们可以定义 \(dp_{i,j,k}\) 表示现在走到了第 \(i\) 个教室,当前换了 \(j\) 次,第 \(i\) 个教室换还是没有换的期望。

除此之外,我们在转移的时候,还要维护一个两个点之间的最短路,因为每次都是从一个点沿着最短路走到另一个点。

这个我们可以用 Floyd 算法实现。用 \(dd_{i,j}\) 表示从 \(i\)\(j\) 的最短路。

考虑对于一个状态 \(dp_{i,j,0/1}\) 如何转移。

显然,根据期望的定义 \(E(x)=\sum x\times P(x)\)

我们可以算出每一种情况下的 \(x\)\(P(x)\),然后累加即可。

然后我们可以对于 \(k\) 进行分类讨论,由于题目要求的是消耗体力值最小,那么我们可以直接对每一种 \(k\) 的情况下的所有情况取最小值即可。

那么分类讨论:

  • 对于 \(dp_{i,j,0}\)(当前节点未交换):

    1. \(dp_{i-1,j,0}\) 转移(上一个节点未交换):这个是最容易的。显然有:

      \[case_1=dp_{i-1,j,0}+dd_{c[i-1],c[i]} \]

      相当于直接从第 \(i-1\) 个点走到 \(i\),直接加上最短路即可。

    2. \(dp_{i-1,j,1}\) 转移(上一个节点交换):

      两种情况: 上一个节点换成功了/没换成功。

      \(k_{i-1}\) 的概率换成功了,\(1-k_{i-1}\) 的概率没换成功。(注意这里是 \(k_{i-1}\) 而不是 \(k_i\)。)

      如果换成功了,那么上一个节点就是 \(d[i-1]\),没换成功上一个节点还是 \(c[i-1]\)

      当前节点都是 \(c[i]\)

      那么有:

      \[case_2=dp_{i-1,j,1}+dd_{d[i-1],c[i]}\times k_{i-1}+dd_{c[i-1],c[i]}\times (1-k_{i-1}) \]

    那么 \(dp_{i,j,0}=\min(case_1,case_2)\)

  • 对于 \(dp_{i,j,1}\)(当前节点交换):

    1. \(dp_{i-1,j-1,0}\) 转移(上一个节点未交换):

      也就是说上一个节点没有进行交换。

      两种情况。这个节点换成功/没换成功。

      \(k_i\) 的概率换成功了,\(1-k_i\) 的概率没换成功。

      显然上一个节点都是 \(c[i-1]\)

      如果换成功了,那么当前节点是 \(d[i]\),没换成功就还是 \(c[i]\)

      那么有:

      \[case_3=dp_{i-1,j-1,0}+dd_{c[i-1],d[i]} \times k_i +dd_{c[i-1],c[i]}\times(1-k_i) \]

    2. \(dp_{i-1,j-1,1}\) 转移(上一个节点交换):

      这种情况是最复杂的。二次分类讨论。

      首先对于上一个节点:有交换成功和没成功两种可能。

      对于交换成功,上一个节点是 \(d[i-1]\);对于没换成功,上一个节点是 \(c[i-1]\)

      \(k_{i-1}\) 的概率交换成功,有 \(1-k_{i-1}\) 的概率没换成功。

      对于当前节点:也有交换成功和没成功两种可能。

      对于交换成功,当前节点是 \(d[i]\);对于没换成功,当前节点是 \(c[i]\)

      \(k_i\) 的概率交换成功,有 \(1-k_i\) 的概率没换成功。

      将上述每种情况的两种情况分别匹配。

      可以得到以下四种情况:

      • 上一个节点换成功,这个节点也换成功;

      • 上一个节点换成功,这个节点没换成功;

      • 上一个节点没换成功,这个节点换成功;

      • 上一个节点没换成功,这个节点也没换成功。

      总共 4 种情况。总的递推式就是:

      \[\begin{aligned} case_4&=dp_{i-1,j-1,1}+dd_{d[i-1],d[i]} \times k_{i-1} \times k_i\\&+dd_{d[i-1],c[i]} \times k_{i-1} \times (1-k_i)\\&+dd_{c[i-1],d[i]}\times (1-k_{i-1})\times k_i\\&+dd_{c[i-1],c[i]}\times (1-k_{i-1})\times (1-k_i)\end{aligned} \]

    那么 \(dp_{i,j,1}=\min(case_3,case_4)\)

最后的答案就是 \(ans= \min(dp_{n,i,0},dp_{n,i,1})(0 \le i \le m)\)

易错点

  1. 用 Floyd 算法时,注意中间节点是在最外层循环(而不是最内层);
  2. 这道题非常复杂,要注意不要漏掉任何一种情况,按顺序分类讨论;
  3. 字母下标 +1 -1 容易搞错。
  4. 由于最后求的期望,\(k\) 数组和 \(dp\) 数组都要设为 double 类型,而不是 int。
  5. 初始化时,记得让所有 \(dd_{i,i}=dd_{i,0}=dd_{0,i}=0\),以及 \(dp_{1,0,0}=dp_{1,1,1}=0\)。对于其它所有的 \(dp_{i,j,0/1}\) 都初始化为 INF。

代码实现

/luoguP1850
#include<iostream>
#include<cstdio>
#include<cstring>
#include<iomanip>
#define int long long
using namespace std;
const int maxn=2005;
int dd[maxn][maxn];
int c[maxn],d[maxn];
double k[maxn],dp[maxn][maxn][2],ans;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    return x*f;
 } 

signed main()
{
    int n,m,v,e;
    n=read();m=read();v=read();e=read();
    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++)cin>>k[i];
    memset(dd,0x3f,sizeof(dd));
    for(int i=1;i<=e;i++)
    {
        int a,b,w;
        a=read();b=read();w=read();
        dd[a][b]=min(dd[a][b],w);
        dd[b][a]=min(dd[b][a],w);
    }
    //floyed 预处理 dd[i][j]:i 到 j 的最短路
    for(int k=1;k<=v;k++)
    {
        for(int i=1;i<=v;i++)
        {
            for(int j=1;j<=v;j++)
            {
                dd[i][j]=min(dd[i][j],dd[i][k]+dd[k][j]);
            }
         } 
     } 
    for(int i=1;i<=v;i++)dd[i][i]=dd[i][0]=dd[0][i]=0; 
    //求 dp[i][j][0]:走到了第 i 个教室,换了 j 次,当前教室换还是不换的方案数。 
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=m;j++)dp[i][j][0]=dp[i][j][1]=100000000000000000.0;
    }
    dp[1][0][0]=dp[1][1][1]=0;
    for(int i=2;i<=n;i++)
    {
        dp[i][0][0]=dp[i-1][0][0]+dd[c[i-1]][c[i]];
        for(int j=1;j<=min(i,m);j++)
        {
            dp[i][j][0]=min(dp[i-1][j][0]+dd[c[i-1]][c[i]],dp[i-1][j][1]+dd[d[i-1]][c[i]]*k[i-1]+dd[c[i-1]][c[i]]*(1-k[i-1]));
            dp[i][j][1]=dp[i-1][j-1][0]+dd[c[i-1]][d[i]]*k[i]+dd[c[i-1]][c[i]]*(1-k[i]); 
            dp[i][j][1]=min(dp[i][j][1],dp[i-1][j-1][1]+dd[d[i-1]][d[i]]*k[i]*k[i-1]+dd[c[i-1]][d[i]]*(1-k[i-1])*k[i]+dd[d[i-1]][c[i]]*k[i-1]*(1-k[i])+dd[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i]));
        }
      }
    //统计答案
    ans=100000000000000000.0;
    for(int i=0;i<=m;i++)ans=min(ans,min(dp[n][i][0],dp[n][i][1]));
    cout<<fixed<<setprecision(2)<<ans<<endl;
    return 0;
}
posted @ 2022-05-01 21:31  向日葵Reta  阅读(61)  评论(0编辑  收藏  举报