期望DP

T1

状态的定义

dp[i][j]表示从(i,j)出发到达目标点的期望移动步数(也有别的定义方法可以顺推什么的,这里不做解释)

答案

显而易见是dp[1][1]

初始化

dp[r][c]=0

转移方程

dp[i][j]=(dp[i][j+1]+2)*r[i][j]+(dp[i+1][j]+2)*d[i][j]+(dp[i][j]+2)*s[i][j]
分别表示向右,向下和留在原地的可能。
然后移个项得到下式
dp[i][j]=((dp[i][j+1]+2)*r[i][j]+(dp[i+1][j]+2)*d[i][j]+2*s[i][j])/(1-s[i][j]);
不过题目里似乎出现了虽然不是最后一个但是s[i][j]=1的情况所以说要特判一下…

#include<bits/stdc++.h>
#define db double
#define eps 1e-5
using namespace std;

void read(int &x){
    x=0; char c=getchar();
    for (; c<'0'; c=getchar());
    for (; c>='0'; c=getchar())x=(x<<3)+(x<<1)+(c^'0'); 
}
db dp[1005][1005],r[1005][1005],d[1005][1005],s[1005][1005];
const int o=0;
int main(){
    int n,m,i,j;
    for (; scanf("%d%d",&n,&m)!=EOF; ){
        for (i=1; i<=n; i++)for (j=1; j<=m; j++)scanf("%lf%lf%lf",&s[i][j],&r[i][j],&d[i][j]);
        memset(dp,0,sizeof(dp));
        for (i=n; i; i--){
            for (j=m; j; j--){
                if (i==n&&j==m)continue;
                if (fabs(s[i][j]-1)<eps)continue;

                dp[i][j]=((dp[i][j+1]+2)*r[i][j]+(dp[i+1][j]+2)*d[i][j]+2*s[i][j])/(1-s[i][j]);
            }
        }
        printf("%.3f\n",dp[1][1]);  
    }
    return (o-o);
}

T2

大概是整个专题里最难的了…
首先每个点可能走到它的父亲节点或者1节点或者儿子节点。然后dp式子就有点儿复杂了。

定义

干脆点用dp[i]表示到达从i走出去的期望步数

答案

dp[1]

剩下具体内容见code

#include<bits/stdc++.h>
#define db double
#define M 10005
#define eps 1e-10
using namespace std;

void read(int &x){
    x=0; char c=getchar();
    for (; c<'0'; c=getchar());
    for (; c>='0'; c=getchar())x=(x<<3)+(x<<1)+(c^'0'); 
}
const int o=0;
db a[M],b[M],c[M],e[M],k[M];
int nx[M],ecnt,cnt[M];
struct edge{int x,nx;}ed[M<<1];

void add(int x,int y){
    ed[ecnt]=(edge){y,nx[x]}; cnt[x]++;
    nx[x]=ecnt++;   
    ed[ecnt]=(edge){x,nx[y]}; cnt[y]++;
    nx[y]=ecnt++;   
}

/*
    dp[i] 表示从节点i成功走出的期望步数 
    对于叶子节点: dp[x]=0*e[x]+dp[1]*k[x]+(1-k[x]-e[x]+1)*dp[fa[x]]
                    =dp[1]*k[x]+(1-k[x]-e[x]+1)*dp[fa[x]]
    对于非叶子节点:    dp[x]=0*e[x]+dp[1]*k[x]+(1-k[x]-e[x])*(dp[fa]+dp[son[x][0]]+dp[son[x][1]]...+dp[son[x][m-1]]+E[x].size())/(E[x].size()) 
                    =dp[1]*k[x]+(1-k[x]-e[x])*(dp[fa]+dp[son[x][0]]+dp[son[x][1]]...+dp[son[x][m-1]])/(E[x].size())
    然后题解有一步玄妙操作 对于每一个点x
        dp[x]=a[x]*dp[1]+b[x]*dp[fa[x]]+c[x]

    对于所有叶子节点
        dp[x]=dp[1]*k[x]+(1-k[x]-e[x])*dp[fa[x]]
        a[x]=k[x] b[x]=1-k[x]-e[x] c[x]=1-k[x]-e[x]

    然后对于所有非叶子节点 直接推...
    dp[1]=a[x]*dp[1]+b[x]*0+c[1]
    答案是c[1]/(1-a[1]) 
*/


bool dfs(int f,int x){
    db p=1.0/cnt[x];
    a[x]=k[x];
    b[x]=(1-k[x]-e[x])*p;
    c[x]=1-k[x]-e[x];
    int i,to;
    db tmp=0;
    for (i=nx[x];i;i=ed[i].nx)if (ed[i].x!=f){
        to=ed[i].x;
        if (!dfs(x,to))return 0;
        a[x]+=(1-k[x]-e[x])*p*a[to];
        c[x]+=(1-k[x]-e[x])*p*c[to];
        tmp+=(1-k[x]-e[x])*p*b[to];         
    }
    if (fabs(1-tmp)<eps)return 0;
    a[x]/=(1-tmp);
    b[x]/=(1-tmp);
    c[x]/=(1-tmp);
    return 1;   
}

int main(){
    int t,ca=0,n,i,j,x,y,z,K,E;
    read(t);
    for (; t; t--){
        read(n);
        memset(nx,0,sizeof(nx)); memset(cnt,0,sizeof(cnt));
        ecnt=1;
        for (i=1; i<n; i++){read(x); read(y); add(x,y);}
        for (i=1; i<=n; i++){read(K); read(E); k[i]=K/100.0; e[i]=E/100.0;}
        printf("Case %d: ",++ca);
        if (dfs(0,1)&&fabs(1-a[1])>eps){printf("%.6f\n",c[1]/(1-a[1]));}
        else printf("impossible\n");
    }
    return (o-o);
}

总之就是很厉害很神奇…如果比赛的时候遇见的话,大概是尽可能推式子然后听天命…

之后的题目注解基本都写在代码里了所以不会太多解释。

T3

大概就是分析一下哪种格子能走到哪种格子就好了。
0 1 2分别表示左脚,右脚和双脚都行

#include<bits/stdc++.h>
#define db double
using namespace std;

void read(int &x){
    x=0; char c=getchar();
    for (; c<'0'; c=getchar());
    for (; c>='0'; c=getchar())x=(x<<3)+(x<<1)+(c^'0');
}
/*
    大概就是有四种格子,然后每种格子出现的概率随机而且不一样。
    不过这不重要(x)

    0什么都不能干

    1->2
    1->3

    2->1
    2->3 

    3->1
    3->2

*/

db dp[4005][4],p[4005][4];
const int o=0;
int main(){
    int t,n,a,b,i,j;
    read(t);
    for (;t;t--){
        read(n); read(a); read(b);
        memset(p,0,sizeof(p));
        for (i=1; i<=n; i++)scanf("%lf%lf%lf%lf",&p[i][0],&p[i][1],&p[i][2],&p[i][3]);
        for (i=n+1; i<=n*2; i++)p[i][3]=1;
        memset(dp,0,sizeof(dp));
        dp[n][1]=dp[n][2]=dp[n][0]=1;
        for (i=n-1; i>=0; i--){
            db tmp0=1,tmp1=1,tmp2=1;
            for (j=i+a; j<=i+b; j++){
                dp[i][0]+=tmp0*(p[j][2]*(dp[j][1]+1.0)+p[j][3]*(dp[j][2]+1.0));
                dp[i][1]+=tmp1*(p[j][1]*(dp[j][0]+1.0)+p[j][3]*(dp[j][2]+1.0));
                dp[i][2]+=tmp2*(p[j][3]*(dp[j][2]+1.0)+p[j][1]*(dp[j][0]+1.0)+p[j][2]*(dp[j][1]+1.0));
                tmp0*=(p[j][0]+p[j][1]);
                tmp1*=(p[j][0]+p[j][2]);
                tmp2*=p[j][0];
            }       
        }
        printf("%.8f\n",dp[0][2]);      
    }
    return (o-o);
}

好了哪天回来再补吧…。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值