P6226 [BalticOI 2019] 潜艇 / Nautilus 题解

P6226 [BalticOI 2019] 潜艇 / Nautilus 题解

link.

貌似是校内模拟赛第一道场切的青?写篇题解祭下。

其实是听到一车同学都过了后才去冲正解的……

下文用 \(c_{i,j}\) 表示地图第 \(i\) 行第 \(j\) 列,\(s_i\) 表示信号的第 \(i\) 个字符。

对于子任务一,直接 \(O(RCM)\) 的暴力枚举就好了,而且大概率跑不满。

对于子任务二,考虑 DP。设 \(f_{i,j,k}\) 表示走完第 \(k\) 步,第 \(i\) 行第 \(j\) 列是否可成为终点。

首先考虑 \(k=0\) 的情况。因为没有限制,所以只要 \(c_{i,j}\) 不是 # 就可以了。

然后考虑 \(k\neq 0\) 的情况。

首先 \(f_{i,j,k}\) 肯定是由 \(f_{a,b,k-1}\) 推来的,至于 \(a\)\(b\) 是什么具体看 \(s_k\)

然后来分讨,根据当前位置和移动信息反推上一步的位置。

如果 \(c_{i,j}\) 是岩石则无论如何都不行。

如果 \(s_k\)N,那么 \(f_{i,j,k}\)\(f_{i+1,j,k-1}\) 推来。

如果 \(s_k\)S,那么 \(f_{i,j,k}\)\(f_{i-1,j,k-1}\) 推来。

如果 \(s_k\)W,那么 \(f_{i,j,k}\)\(f_{i,j+1,k-1}\) 推来。

如果 \(s_k\)E,那么 \(f_{i,j,k}\)\(f_{i,j-1,k-1}\) 推来。

如果 \(s_k\)?,则可以从四个方向任意挑选符合条件的。

注意边界问题。

放个代码:

for(int k=0,o,t;k<=l;++k)
	{
		for(int i=1;i<=n;++i)
		{
			for(int j=1;j<=m;++j)
			{
				if(ch[i][j]=='#'){f[i][j][k]=0;continue;}
				if(!k){f[i][j][k]=1;continue;}
				if(s[k]=='S'&&i-1>0)f[i][j][k]=f[i-1][j][k-1];
				if(s[k]=='W'&&j+1<=m)f[i][j][k]=f[i][j+1][k-1];
				if(s[k]=='N'&&i+1<=n)f[i][j][k]=f[i+1][j][k-1];
				if(s[k]=='E'&&j-1>0)f[i][j][k]=f[i][j-1][k-1];
				if(s[k]=='?')
				{
					if(i-1>0)f[i][j][k]|=f[i-1][j][k-1];
					if(j+1<=m)f[i][j][k]|=f[i][j+1][k-1];
					if(i+1<=n)f[i][j][k]|=f[i+1][j][k-1];
					if(j-1>0)f[i][j][k]|=f[i][j-1][k-1];
				}
			}
		}
	}

好的恭喜你现在已经有了 \(66\) 分。

接下来考虑第三个子任务。

然后发现 \(f_{i,j,k}\) 的转移只和 \(f_{a,b,k-1}\) 有关,所以考虑滚动数组,滚掉 \(k\) 的那一维。

不会的请出门右转。

然后我赛时因为滚动完没清空被硬控十分钟,还是太菜了。

接下来观察代码。

发现空间没问题,时间略超,DP 数组全是 bool 类型的变量。

再联想这次是数据结构专题。

很自然想到用 bitset 压缩时间。

具体地,把 \(f_{i,j}\) 压缩成 \(f_i\)

然后考虑转移。

\(s_k\)NS 的很简单,只要把 \(i\) 改一改就好了。

那么要修改 \(i\) 的每一位呢?

考虑左移和右移操作。

重点:bitset 的下标是从右往左编号,而非从左往右!

所以如果本来是 \(j-1\) 的操作,要变成左移一位,\(j+1\) 的操作则是右移一位。

然后我们发现漏了岩石的情况。毕竟不可能一位一位判断。

此时要充分利用人类智慧,考虑将原图的每一行也压成 bitset,其中岩石为 \(0\),水为 \(1\),第 \(i\) 行的情况设为 \(g_i\)

然后在每个 \(f_i\) 算完后按位与上 \(g_i\)。就可以排除岩石的干扰。

这为什么是对的呢?

我们继续分讨。

如果 \(g_{i,j}\)\(0\),那么按位与上 \(f_{i,j}\) 一定为 \(0\),对应不合法情况。

如果 \(g_{i,j}\)\(1\),那么按位与上 \(f_{i,j}\) 结果不变,不影响答案。

证毕。

然后时间复杂度是 \(O(\frac{RCM}{\omega})\),其中 \(\omega\)\(32\)。刚好能卡过。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ljl;
const int N=505;
int n,m,l,ans;
char ch[N][N];
string s;
bitset<N> f[2][N],g[N];
int main(){
	ios::sync_with_stdio(0);
	cin>>n>>m>>l;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			cin>>ch[i][j];
	cin>>s;s=" "+s;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			f[0][i][j]=(ch[i][j]=='#'?0:1);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			g[i][j]=(ch[i][j]=='#'?0:1);
	for(int k=1,o,t;k<=l;++k)
	{
		o=k&1;t=o^1;
		for(int i=1;i<=n;++i)f[o][i].reset();
		if(s[k]=='S')//i-1>0
		{
			for(int i=2;i<=n;++i)
				f[o][i]=f[t][i-1];
//			for(int i=2;i<=n;++i)
//			{
//				for(int j=1;j<=m;++j)
//				{
//					f[o][i][j]=f[t][i-1][j];
//				}
//			}
		}
		if(s[k]=='N')
		{
			for(int i=1;i<n;++i)f[o][i]=f[t][i+1];
//			for(int i=1;i<n;++i)
//			{
//				for(int j=1;j<=m;++j)
//				{
//					f[o][i][j]=f[t][i+1][j];
//				}
//			}
		}
		if(s[k]=='W')//j<m
		{
			for(int i=1;i<=n;++i)
				f[o][i]=f[t][i]>>1;
//			for(int i=1;i<=n;++i)
//			{
//				for(int j=1;j<m;++j)
//				{
//					f[o][i][j]=f[t][i][j+1];
//				}
//			}
		}
		if(s[k]=='E')//j>1
		{
			for(int i=1;i<=n;++i)
				f[o][i]=(f[t][i]<<1);
//			for(int i=1;i<=n;++i)
//			{
//				for(int j=2;j<=m;++j)
//				{
//					f[o][i][j]=f[t][i][j-1];
//				}
//			}
		}
		if(s[k]=='?')
		{
			for(int i=2;i<=n;++i)
				f[o][i]|=f[t][i-1];
			for(int i=1;i<=n;++i)
				f[o][i]|=f[t][i]<<1;
			for(int i=1;i<n;++i)
				f[o][i]|=f[t][i+1];
			for(int i=1;i<=n;++i)
				f[o][i]|=f[t][i]>>1;
		}
		for(int i=1;i<=n;++i)f[o][i]&=g[i];
	}
	for(int i=1;i<=n;++i)
		ans+=f[l&1][i].count();
	cout<<ans<<'\n';
	return 0;
}

AC 记录。

posted @ 2026-07-08 22:54  Atserckcn  阅读(3)  评论(0)    收藏  举报