NOIP2016 换教室 期望DP

来源:互联网 发布:阿里弹性云 编辑:程序博客网 时间:2024/05/16 06:09

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[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;}
原创粉丝点击