BZOJ2246 SDOI2011 迷宫探险(状压+概率dp)

题面太长了,贴起来好麻烦,走链接吧:
P2489 [SDOI2011]迷宫探险

题目指向状压。自然地考虑用二进制表示状态,0为无害,1为有害。紧接着会发现,当我们走到某个点 ( x, y ) 时,我们并不一定能确定地图上其它点是否无害。(比如说,在我们走到陷阱A的第一个位置之前,我们不能确定A是否有害,而我们完全有可能在走到 ( x, y)之前没有去过A)。

所以,应该用三进制表示状态:0为无害,1为有害,2为未知。
假定我们已经处理出一个数组 g,g[ S ][ j ] 表示在S状态下第 j 类陷阱有害的概率。

显然我们可设四维状态: f[ x ][ y ][ S ][ hp ] 表示走到 ( x, y ) 、状态为 S 、生命值为 hp 时存活的概率。
最终我们要输出的是 f[ sx ][ sy ][ Power[ k ]-1 ][ h ],sx、sy为起点,Power[ k ]-1 即为全部未知的状态。因为我们起初着手解决这个问题时面对的就是这样的状态。

以下默认S是三进制表示。

那么考虑状态转移方程:
设 ( tx, ty ) 为 ( x,y ) 的下一步,t 为 ( tx, ty ) 的陷阱类型(处理为数字表示)。
当 hp=0 时,f[ x ][ y ][ S ][ hp ] =0;
当 ( x, y ) 为终点时, f[ x ][ y ][ S ][ hp ] =1;
当 ( x, y ) 确认为无害陷阱或为起点、终点、平地时,f[ x ][ y ][ S ][ hp ] = max{ f[ tx ][ ty ][ S ][ hp ] };
当 ( x, y ) 确认有害时, f[ x ][ y ][ S ][ hp ] = max { f[ tx ][ ty ][ S ][ hp-1 ] };(要掉血)
当 ( x, y ) 未知时, f[ x ][ y ][ S ][ hp ] =max { g[ S ][ t ] * f[ tx ][ ty ][ S’ ][ hp-1 ] + ( 1 - g[ S ][ t ] ) * f[ tx ][ ty ][ S” ][ hp ]};

其中 S’ 表示在原来S的基础上把第 t 位状态改为 1, S” 表示第 t 位状态改为 0。如何实现呢?我们先看 g 数组。

由于状态是三进制,所以很多操作都比二进制麻烦得多。但是在理解的时候当成二进制or十进制数会比较容易想通。
方便起见,我们预处理 Power[ i ] 表示 3 ^ i 。
读入的 2 ^ k 个数存在数组 p 中。

外循环枚举S,内循环枚举 i(此处 i 表示 p的下标),接下来我们要做三件事。

第一,判断当前 S 与 i 是否冲突。比如 i 的第 j 位为1,表示当前状态下陷阱 j 有害,那么如果S 的第 j 位为 0,则会造成冲突。

int Take(int S,int j)
{
    return (S/Power[j-1])%3;
}

for(int j=1;j<=k;++j)
{
    int x=Take(S,j);//取出S中的第j位。具体为什么用十进制的感觉意会一下,很容易想通。
    if(x==2) continue;
    if(x!=((i>>(j-1))&1)){//不一样则冲突。
         flag=0;break;
    }
}

第二,将 i 状态下每一个在的 g[ S ][ j ] 累加 p[ i ]。

for(int j=1;j<=k;++j)
{
    int x=Take(S,j);
    if(x==2&&((i>>(j-1))&1)) g[S][j]+=p[i];
}

没什么好说的。

最后,因为 g[ S ][ j ] 表示的是概率。这里引入一个公式: P( A | B ) = P ( A·B ) / P( B )。
即,在B已发生的条件下,A发生的概率为AB同时发生的概率除以B发生的概率。没有系统地学过这一块,只能借例说明了。比如当前有三个互斥事件X、Y、Z,A为X、Y均发生的概率,B为Y、Z均发生的概率。那么显然可得了。
具体在这道题中,A可为状态S下 j 有害,B可为状态S下的所有合法情况。那么我们开一个变量Pb,在做完第一件事后 Pb+=p[ i ] ,则据公式可得 g[ S ][ j ] /= Pb。

绕回来,上面求 S’ 和 S” 的实现办法。

int Remake(int S,int j,bool flag)
{
    return(S-Take(S,j)*Power[j-1]+flag*Power[j-1]);
}

luogu题解里学到的小技巧。感觉很妙。flag传的是你想要修改成的第 j 位的状态。很优美地完成了。

还有一点要解释。状态转移方程里提及 “ 当 ( x, y ) 确认为无害陷阱或为起点、终点、平地时,f[ x ][ y ][ S ][ hp ] = max{ f[ tx ][ ty ][ S ][ hp ] } ” ,为什么是起点也要执行呢?起初我以为没有影响,但是实践证明确实会影响最终答案。在杨神的睿智引导(?下,是因为对于某个点 ( x, y ),假定有两条路径可以走向终点,路径 p1 不经过起点且较小,另一条路径 p2 经过起点且概率较大(就算 p2 包含了从起点走向另一条路的一部分,但是对于 ( x, y) 它也是要考虑的)。因为我们是取 max,如果不处理经过起点的情况,那么无疑会导致 ( x, y ) 的值偏小。实际上WA的结果也确实是这样。

至此就没有其它大问题要阐明的了。虽然用了很多时间也调了很久,但是终于做出来并且敲完这篇博客,还是非常开心的。

细节的处理见code。

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

int m,n,k,h;
char Map[35][35];
int sx,sy;
int p[35];

int Power[6];

double g[245][6];
double f[35][35][245][6];
bool vis[35][35][245][6];

int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};

int Take(int S,int j)
{
    return (S/Power[j-1])%3;
}

int Remake(int S,int j,bool flag)
{
    return(S-Take(S,j)*Power[j-1]+flag*Power[j-1]);
}

void Init()
{
    scanf("%d%d%d%d",&m,&n,&k,&h);
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=m;++i)
        for(int j=1;j<=n;++j)
        {
            char s=getchar();
            for(;(s<'A'||s>'E')&&s!='.'&&s!='@'&&s!='$'&&s!='#';s=getchar());
            Map[i][j]=s;
            if(s=='$') sx=i,sy=j;
        }
    for(int i=0;i<(1<<k);++i) scanf("%d",&p[i]); 
}

void Prepare()
{
    Power[0]=1;
    for(int i=1;i<=k;++i) Power[i]=(Power[i-1]<<1)+Power[i-1];

    memset(g,0,sizeof(g));
    for(int S=0;S<Power[k];++S)//枚举状态[ 0,Power[k] )。
    {
        int Pb=0;
        for(int i=0;i<1<<k;++i)
        {
            int flag=1;
            for(int j=1;j<=k;++j)
            {
                int x=Take(S,j);//取出S中的第 j 位。 
                if(x==2) continue;
                if(x!=((i>>(j-1))&1)){// 是否冲突。 
                    flag=0;break;
                }
            }
            if(!flag) continue;
            Pb+=p[i];
            for(int j=1;j<=k;++j)
            {
                int x=Take(S,j);
                if(x==2&&((i>>(j-1))&1)) g[S][j]+=p[i];
            }//将i状态下每一个在的g[S][j]累加 p[i]。
        } 
        for(int j=1;j<=k;++j) g[S][j]/=(1.000*Pb);
    }
}

double dp(int x,int y,int S,int hp)
{   
    if(vis[x][y][S][hp]) return f[x][y][S][hp];
    vis[x][y][S][hp]=1;
    if(hp==0) return f[x][y][S][hp]=0.000;
    if(Map[x][y]=='@') return f[x][y][S][hp]=1.000;

    for(int i=0;i<4;++i)
    {
        int tx=x+dx[i];
        int ty=y+dy[i];
        int t=Map[tx][ty]-'A'+1;

        if(Map[tx][ty]=='#'||tx<1||tx>m||ty<1||ty>n) continue;
        if(Map[tx][ty]=='@'||Map[tx][ty]=='.'||Map[tx][ty]=='$'||(t>0&&t<=k&&Take(S,t)==0)) f[x][y][S][hp]=max(f[x][y][S][hp],dp(tx,ty,S,hp));
        if(t>0&&t<=k&&Take(S,t)==1) f[x][y][S][hp]=max(f[x][y][S][hp],dp(tx,ty,S,hp-1));
        if(t>0&&t<=k&&Take(S,t)==2) f[x][y][S][hp]=max(f[x][y][S][hp],g[S][t]*dp(tx,ty,Remake(S,t,1),hp-1)+(1-g[S][t])*dp(tx,ty,Remake(S,t,0),hp));
    }

    return f[x][y][S][hp];
}

void Work()
{
    memset(f,0,sizeof(f));
    printf("%.3lf",dp(sx,sy,Power[k]-1,h));
}

int main()
{
    Init();
    Prepare();
    Work();
    return 0;
}

感谢中国。

猜你喜欢

转载自blog.csdn.net/hfl030/article/details/80954478