NOIP2016 换教室 期望DP

Problem

嗯,NOIP历年真题什么的,到处都有,抓一道就是三四倍经验题,我就不写题目描述了23333
偷个懒一定不会被人发现的

Solution

Thoughts

事实上,这是我第一次做概率与期望DP的题目,不是很懂套路,然后就想了很久很久,没什么太多的头绪。首先我觉得应该是用f[i][j]保存前i个时间段,申请换j次课,期望收获到的疲劳值。但是注意到这样的状态还是很复杂难以转移,因为状态转移后的状态有两种可能性,他有可能停在c[i]教室,也有可能停在d[i]教室。
可以联想到 [uva-1336修缮长城] 的那道题,通常为了避免复杂状态,我们通过加维来解决。所以用f[i][j][0]保存上次未申请的期望疲劳值,f[i][j][1]表示上次申请的期望疲劳值。但是状态转移方程有点让我纠结。

题外话

在dp的时候,就需要乘上事件发生的概率,我一直在纠结的是,发现好像有些情况下只乘一个概率似乎少了点什么,纠结了很久,最后问了问旁边的dalao,果然如此。这是期望DP的套路啊。感觉吃枣药丸。

Solution

那么由上我想到的思路,就可以很快地得出dp方程,如下。为了让方程看起来好看一点,我们用cc表示从c[i-1]教室到c[i]教室的路径,用cd表示c[i-1]教室到d[i]教室的路径,dc,dd以此类推。
巨长无比的方程
其次,注意到这里的对f[i-1]的调用,每次更新时都只有j,j-1对当前更新的j有影响,那么我们可以采用滚动数组的思想,将j倒叙循环,这样得到的方程可以大大降低空间复杂度。
简化后的方程
另外,f[i][0][0]是需要特判的,它仅能从f[i-1][0][0]上转移过来,因此,也有 f[j][0]+=cc; f [ j ] [ 0 ] + = c c ; 需要注意的是,这个更新要在更新完前面的 f[1..j][0/1] f [ 1.. j ] [ 0 / 1 ] 之后才能更新。

Code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
struct data{
    int v,w,nxt;
}edge[180010];
const int INF=0x3f3f3f3f;
int n,m,v,e,p,head[310],c[2010],d[2010],dis[310][310];
double ans=INF,k[2010],f[2010][2];
bool inq[310];
queue<int> q;
template <typename Tp> inline void read(Tp &x)
{
    x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
inline double fmin(double x,double y){return x<y?x:y;}
inline void insert(int u,int v,int w)
{
    edge[++p].v=v;edge[p].w=w;
    edge[p].nxt=head[u];head[u]=p;
    edge[++p].v=u;edge[p].w=w;
    edge[p].nxt=head[v];head[v]=p;
}
void input()
{
    read(n),read(m),read(v),read(e);
    int u,v,w;
    for(int i=1;i<=n;i++)
      read(c[i]);
    for(int i=1;i<=n;i++)
      read(d[i]);
    for(int i=1;i<=n;i++)
      scanf("%lf",&k[i]);
    for(int i=1;i<=e;i++)
    {
        read(u),read(v),read(w);
        if(u==v)
          continue;
        insert(u,v,w);
    }
}
void spfa(int u)
{
    int x,y;
    memset(inq,0,sizeof(inq));
    for(int i=1;i<=v;i++)
      dis[u][i]=INF;
    dis[u][u]=0;
    while(!q.empty())
      q.pop();
    q.push(u);
    inq[u]=true;
    while(!q.empty())
    {
        x=q.front();
        q.pop();
        inq[x]=false;
        for(int i=head[x];i;i=edge[i].nxt)
        {
            y=edge[i].v;
            if(dis[u][y]>dis[u][x]+edge[i].w)
            {
                dis[u][y]=dis[u][x]+edge[i].w;
                if(!inq[y])
                  q.push(y),inq[y]=true;
            }
        }
    }
}
int main()
{
    int cc,cd,dc,dd;
    double t;
    input();
    for(int i=1;i<=v;i++)
      spfa(i);
    for(int j=0;j<=m;j++)
      f[j][0]=f[j][1]=INF;
    f[0][0]=f[1][1]=0.0;
    for(int i=2;i<=n;i++)
    {
        cc=dis[c[i-1]][c[i]];cd=dis[c[i-1]][d[i]];
        dc=dis[d[i-1]][c[i]];dd=dis[d[i-1]][d[i]];
        for(int j=(i<m?i:m);j>=1;j--)
        {
            t=fmin(f[j][0]+cc,f[j][1]+k[i-1]*dc+(1.0-k[i-1])*cc);//上次不换/换了
            f[j][1]=fmin(f[j-1][0]+k[i]*cd+(1.0-k[i])*cc,f[j-1][1]+dd*k[i-1]*k[i]+cd*(1.0-k[i-1])*k[i]+dc*k[i-1]*(1.0-k[i])+cc*(1.0-k[i-1])*(1.0-k[i]));//上次不换/换了
            f[j][0]=t;
        }
        f[0][0]+=cc;
    }
    for(int j=0;j<=m;j++)
    {
        if(ans>f[j][0])
          ans=f[j][0];
        if(ans>f[j][1])
          ans=f[j][1];
    }
    printf("%.2lf\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值