P2489 [SDOI2011]迷宫探险 题解

题意简述:

一个 \(n\times m\) 的带墙体单入口多出口迷宫中有 \(k\) 个陷阱,陷阱分为有害或无害,有害会使人掉血,给出所有垃圾的有害与无害的所有排列组成的概率,给定人的血量,求掉最少血走出迷宫的概率。

解:

提到迷宫问题,考虑搜索。

首先将垃圾状态状压,\(0\) 为未知,\(1\) 为无害,\(2\) 为有害,由于一来所有陷阱的情况都是未知的,所以状态为 \(0\),而人需要去试探陷阱,每次试探后分有害无害搜索两种垃圾状态搜下去即可。

很明显会 \(T\),考虑优化,在迷宫中有限考虑记忆化。

\(f_{i,j,s,h}\) 代表人在 \((i,j)\),陷阱状态为 \(s\),生命值为 \(h\) 的概率。

那么我们需要提前预处理出来每一个三进制状态中一个未知的陷阱是有害的概率 \(g_{i,j}\)

那么我们找到每一个合法的陷阱的组成方式,那么对于一个未知状态,它的概率即为所有组成方式的概率和除以组成方式个数。

那么记忆化也能转移了,对于踩到未知陷阱,\(f_{i,j,s,h}=f_{i,j,new1,h-1}\times g_{s,k}+f_{i,j,new2,h}\times (1-g{s,k})\),其中 \(new1,new2\) 代表踩到有害、无害陷阱的新状态,而 \(k\) 代表踩的是哪一种陷阱。

如果对于已知陷阱,踩到未知的不扣,踩到已知的扣血即可。

但是这样会有问题,因为有情况可以绕一圈然后再走出去,那么就导致了状态会未算完就转移了。

那么我们用拓扑序就能解决这个问题,对于每一种陷阱组成状态预处理出来自己下一步可以走到的未知、有害陷阱和出口,这样就处理出了这个问题。

预处理只需要枚举状态,枚举起始点坐标,然后对于每一个状态与初始坐标进行广搜记录能走到的点即可。

复杂度:\(O(n\times m\times 3^k\times h)\)

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int N=35;
const int M=750;
const int K=6;
const double eps=1e-10;
int n,m,k,hp,xb,yb;
char s[N][N];
int p[M],power[6]={1,3,9,27,81,243},b[4][2]={{1,0},{0,1},{-1,0},{0,-1}},vis[N][N],cnt,r[M];
double g[M][K],f[N][N][M][K];
vector<pair<int,int>>v[N][N][1<<K];
void dfs(int x,int y,int q,int xx,int yy,int id)
{
	if(vis[x][y]==id)return;
	vis[x][y]=id;
	if((x!=xx||y!=yy)&&(s[x][y]=='@'||(s[x][y]>='A'&&s[x][y]<='Z'&&((1<<(s[x][y]-'A'))&q)==0)))
	{
		//cout<<xx<<" "<<yy<<" "<<q<<" "<<x<<" "<<y<<endl;
		v[xx][yy][q].push_back({x,y});
		return;
	}
	for(int i=0;i<4;i++)
	{
		int xt=x+b[i][0],yt=y+b[i][1];
		if(xt<1||yt<1||xt>n||yt>m||s[xt][yt]=='#'||(xx==xt&&yy==yt))continue;
		dfs(xt,yt,q,xx,yy,id);
	}
}
double dp(int x,int y,int q,int h)
{
	//cout<<x<<" "<<y<<" "<<q<<" "<<h<<endl;
	if(f[x][y][q][h]>=-eps)return f[x][y][q][h];
	if(h==0)return f[x][y][q][h]=0;
	if(s[x][y]=='@')return f[x][y][q][h]=1;
	int len=v[x][y][r[q]].size();
	double res=0;
	for(int i=0;i<len;i++)
	{
		int xx=v[x][y][r[q]][i].first,yy=v[x][y][r[q]][i].second;
		if(s[xx][yy]=='@')
		{
			res=max(res,dp(xx,yy,q,h));
			continue;
		}
		if(q/power[s[xx][yy]-'A']%3==2)res=max(res,dp(xx,yy,q,h-1));
		if(q/power[s[xx][yy]-'A']%3==0)
		{
			int xzt1=q+power[s[xx][yy]-'A']*2,xzt2=q+power[s[xx][yy]-'A'];
			res=max(res,dp(xx,yy,xzt1,h-1)*g[q][s[xx][yy]-'A']+dp(xx,yy,xzt2,h)*(1-g[q][s[xx][yy]-'A']));
		}
	}
	return f[x][y][q][h]=res;
}
int main()
{
	//freopen("maze.in","r",stdin);
	//freopen("maze.out","w",stdout);
	scanf("%d%d%d%d",&n,&m,&k,&hp);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)for(int q=0;q<power[k];q++)for(int t=0;t<=hp;t++)f[i][j][q][t]=-1;
	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='$')xb=i,yb=j;
	for(int i=0;i<(1<<k);i++)scanf("%d",&p[i]);
	for(int i=0;i<power[k];i++)
	{
		int num=0;
		for(int j=0;j<(1<<k);j++)
		{
			bool flag=0;
			for(int q=0;q<k;q++)
			{
				if(i/power[q]%3==0)continue;
				if(i/power[q]%3==1&&(j&(1<<q))==0)continue;
				if(i/power[q]%3==2&&(j&(1<<q)))continue;
				flag=1;
				break;
			}
			if(flag)continue;
			num+=p[j];
			for(int q=0;q<k;q++)
			{
				if(i/power[q]%3)continue;
				if((j&(1<<q)))g[i][q]+=p[j];
			}
		}
		for(int j=0;j<k;j++)
		{
			if(i/power[j]%3==1)g[i][j]=1,r[i]|=(1<<j);
			if(i/power[j]%3==2)g[i][j]=0;
			if(i/power[j]%3==0)g[i][j]/=num;
			//cout<<i<<" "<<j<<" "<<g[i][j]<<endl;
		}
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int q=0;q<1<<k;q++)if(s[i][j]!='#')dfs(i,j,q,i,j,++cnt);
	double ans=dp(xb,yb,0,hp);
	printf("%.3lf",ans);
	return 0;
}
posted @ 2023-02-24 13:45  Gmt丶Fu9ture  阅读(37)  评论(0)    收藏  举报