1 条题解

  • 0
    @ 2025-8-24 21:38:17

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar 传奇英雄
    鞠躬尽瘁,让bug死而后已

    搬运于2025-08-24 21:38:17,当前版本为作者最后更新于2020-06-19 08:41:13,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    **这道题的数据太水了!!强烈建议管理加强数据!!

    传奇英雄,精心制造Hack数据

    https://www.luogu.com.cn/discuss/show/231577

    以上数据可以卡掉部分洛谷和new_BZOJ(vijos)的题解。

    有2种错误的解法:有人用dp[a][b][c][d]表示在(a,b)的位置,状态为c,血量为d,但是可能会遇见环的情况,导致用没有算完的状态更新了答案。(详见上面hack的那个帖子)

    还有人加了一维fa,记录这个点是从哪来的。但是仍然会被卡。

    那么,我们发现这道题的关键在于如何避免产生环。

    本人思路:首先,我们算出当前状态可以到达那些未知或有毒的陷阱。

    然后只考虑向未知或有毒的陷阱转移。

    这样子,显然是没有环的。(因为向有毒的陷阱会掉1滴血,向未知的陷阱会知道未知陷阱的信息,而这样显然是不可逆的)

    我们将到达终点/掉血/未知陷阱作为“一步”,而不是将一次上下左右作为“一步”

    因为到达空地/已知无毒陷阱是“平淡无奇”的,没有意义,我们认为能够直接越过,不考虑。

    然后,我们就愉快地A掉了这个题!

    所以说,大家一定要严谨治学啊!不要以为A掉了就对了,要多加思考。

    最后,祝大家省选愉快!(距离进省队还有2天)

    BJ/HE/SC稳住!天佑CCF,天佑我中华!

    //作者:传奇英雄
    #include<bits/stdc++.h>
    using namespace std;
    const int g=32,g2=245,s[6]={1,3,9,27,81,243},x[4]={1,-1},y[4]={0,0,1,-1};
    int m,n,k,h,sx,sy,u[g2],w,id,state,mark[g][g],m1,m2,m3;
    double dp[g][g][g2][6],p[g2][6];
    bool vis[g][g][g2][6],f[6],v2[g][g][g2];
    char ch[g][g];
    vector< pair<int,int> > v[g][g][g2];
    
    int get(int t,int a)
    {
    	if(u[t]>=0) return u[t];
    	for(int i=a;;i++)
    		if(!(t/s[i]%3))//考虑2种情况,未知的为0或为1
    			return u[t]=get(t+s[i],i+1)+get(t+(s[i]<<1),i+1);
    }
    
    void dfs2(int a,int b)
    {
    	for(int i=0;i<4;i++)
    	{
    		m1=a+x[i],m2=b+y[i];
    		if(m1&&m1<=m&&m2&&m2<=n&&mark[m1][m2]!=id)
    		{
    			mark[m1][m2]=id;//打上标记,不走重复的点
    			if(ch[m1][m2]=='.'||ch[m1][m2]=='$')//空地
    				dfs2(m1,m2);
    			else
    			{
    				if(ch[m1][m2]=='@')//目标
    				{
    					for(int j=1;j<=h;j++)
    					{
    						vis[a][b][state][j]=vis[sx][sy][state][j]=1;
    						dp[a][b][state][j]=dp[sx][sy][state][j]=1;
    					}
    					return;
    				}
    				m3=int(ch[m1][m2])-65;//陷阱
    				if(m3>=0&&m3<k)
    				{
    					if(state/s[m3]%3==1)//无毒
    						dfs2(m1,m2);
    					else
    						v[sx][sy][state].push_back(make_pair(m1,m2));//有毒
    				}
    			}
    		}
    		if(vis[sx][sy][state][1])//已经到达终点
    		{
    			for(int j=1;j<=h;j++)
    			{
    				vis[a][b][state][j]=1;
    				dp[a][b][state][j]=1;
    			}
    			return;
    		}
    	}
    }
    
    double dfs(int a,int b,int c,int d)
    {
    	if(vis[a][b][c][d])
    		return dp[a][b][c][d];
    	if(!v2[a][b][c])//当前状态没有被计算过
    	{
    		v2[a][b][c]=1;//标记已经计算过了,不重复计算
    		sx=a,sy=b,state=c;//记录当前状态
    		mark[a][b]=++id;//标记已经经过的位置,用id标记,省了清空
    		dfs2(a,b);//计算当前状态中能到达的有毒或未知陷阱
    		if(vis[a][b][c][d]) return 1;//已经到达目标
    	}
    	vis[a][b][c][d]=1;
    	double ans=0;
    	for(int i=0;i<v[a][b][c].size();i++)
    	{
    		int m4=v[a][b][c][i].first;
    		int m5=v[a][b][c][i].second;//陷阱的坐标
    		int m6=int(ch[m4][m5])-65;//陷阱的类型
    		if(c/s[m6]%3)//有毒陷阱
    		{
    			if(d>1)//必须血量大于1才能进有毒陷阱
    				ans=max(ans,dfs(m4,m5,c,d-1));
    		}
    		else//未知的陷阱
    			if(d==1)//只有1滴血时只能进无毒的陷阱
    				ans=max(ans,dfs(m4,m5,c+s[m6],d)*p[c][m6]);
    			else//无毒或有毒
    				ans=max(ans,dfs(m4,m5,c+s[m6],d)*p[c][m6]+dfs(m4,m5,c+(s[m6]<<1),d-1)*(1-p[c][m6]));
    		if(ans==1) return dp[a][b][c][d]=1;
    	}
    	return dp[a][b][c][d]=ans;
    }
    
    int main()
    {
    	//freopen("in","r",stdin);
    	scanf("%d%d%d%d\n",&m,&n,&k,&h);
    	for(int i=1;i<=m;i++)
    	{
    		for(int j=1;j<=n;j++)
    		{
    			scanf("%c",&ch[i][j]);
    			if(ch[i][j]=='$')
    				sx=i,sy=j;//起点的位置
    		}
    		scanf("\n");//注意坑人的回车
    	}
    	memset(u,-1,sizeof(u));
    	w=(s[k]-1)>>1;
    	while(w<s[k])//我们定的状态,0未知,1无毒,2有毒
    	{
    		scanf("%d",&u[w]);
    		w++;
    		for(int i=0;;i++)//将输入的编号转化为状态
    			if(f[i])
    			{
    				f[i]=0;
    				w+=s[i];
    			}
    			else
    			{
    				f[i]=1;
    				break;
    			}
    	}
    	for(int i=0;i<s[k];i+=3)//末位为1或2的显然已经不用考虑,因为计算i-1的时候已经算过了,i-1最后1位加1
    		get(i,0);//计算未知的所有可能之和
    	for(int i=0;i<s[k];i++)
    		for(int j=0;j<k;j++)
    			if(!(i/s[j]%3))
    				p[i][j]=u[i+s[j]]*1.0/(u[i+s[j]]+u[i+(s[j]<<1)]);//计算概率
    	printf("%.3f",dfs(sx,sy,0,h));
    	return 0;
    }
    
    

    本题思路来自chdy大佬。他的代码也是对的。感谢大佬!

    不建议删掉错误的题解。因为错误的题解确实也是一种很好的思路。(建议各位大佬学习一下错误的题解,然后认真思考一下为什么错了)做走迷宫问题的时候一定要注意环的问题。

    • 1

    信息

    ID
    1527
    时间
    1000ms
    内存
    125MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者