题目链接
题目大意
有一个
n
∗
m
(
1
≤
n
,
m
≤
500
)
n*m(1\leq n,m\leq 500)
n∗m(1≤n,m≤500)大小的网格,左上角是
(
1
,
1
)
(1,1)
(1,1),右下角是
(
n
,
m
)
(n,m)
(n,m),其中有一些无法通过的格子。
有以下三种机器人:
1.只能向下移动的机器人,即只能从
(
x
,
y
)
(x,y)
(x,y)移动到
(
x
+
1
,
y
)
(x+1,y)
(x+1,y);
2.只能向右移动的机器人,即只能从
(
x
,
y
)
(x,y)
(x,y)移动到
(
x
,
y
+
1
)
(x,y+1)
(x,y+1);
3.既可以向下也可以向右移动的机器人。
有
q
(
1
≤
q
≤
5
×
1
0
5
)
q(1 \leq q \leq 5 \times10^5)
q(1≤q≤5×105)次询问,每次询问
t
t
t型机器人需要从起点
(
x
1
,
y
1
)
(x1,y1)
(x1,y1)前往终点
(
x
2
,
y
2
)
(x2,y2)
(x2,y2),判断是否可行。
题解
对于只会向下和向右移动的机器人,只需要求一次前缀即可,若撞到墙则失败。
对于其他情况,我们设
d
p
x
1
,
y
1
,
x
2
,
y
2
dp_{x_1,y_1,x_2,y_2}
dpx1,y1,x2,y2为从
x
1
,
y
1
x_1,y_1
x1,y1到
x
2
,
y
2
x_2,y_2
x2,y2是否可行。
通过计算,我们发现需要进行
O
(
n
4
)
O(n^4)
O(n4)次操作,超出范围,
而
q
q
q次询问复杂度太大,所以我们考虑将其离线操作,将同一起点或终点的可能计算出来。
所以我们使用bitset来优化代码:
①我们可以通过bitset数组删去前两个维度,计算同一终点的情况,
易得dp数组的转移式为
d
p
x
1
,
y
1
=
d
p
x
1
+
1
,
y
1
∣
d
p
x
1
,
y
1
+
1
∣
(
x
1
,
y
1
)
dp_{x1,y1}=dp_{x1+1,y1}|dp_{x_1,y_1+1}|(x_1,y_1)
dpx1,y1=dpx1+1,y1∣dpx1,y1+1∣(x1,y1)
即当前的可能性或上一步之前的可能性。
我们还可以继续通过bitset来压缩维度,这里我们压去第一维度,则dp转移式更新,
用伪代码实现为:
for(i , n -> 1)
for(j , n -> 1)
dp[j]|=dp[j+1]|(i,j)
则代码的复杂度为
O
(
n
4
l
o
g
)
O(n^4log)
O(n4log),符合要求。
②我们还可以通过bitset删去后两个维度,计算同一起点的情况,
代码实现稍为复杂,先初始化打好起点的方案,然后枚举行,与前一个合并。
由于除核心部分外代码大致相同,故不多赘述,详见代码,这里给出了一份别的选手的代码.
参考代码
//压后两个维度
#include<bits/stdc++.h>
#define eb emplace_back
using namespace std;
const int M=509;
int n,m,q;
char c[M][M];
int X[M][M],Y[M][M];
bool ans[500009];
bitset<M>vis[M];
struct P{
int rx,ly,ry,id;
};
vector<P>p[M],t[M];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%s",c[i]+1);
for(int j=1;j<=m;++j)X[i][j]=X[i][j-1]+(c[i][j]=='1'),Y[i][j]=Y[i-1][j]+(c[i][j]=='1');
}
scanf("%d",&q);
for(int i=1,o,lx,ly,rx,ry;i<=q;++i){
scanf("%d%d%d%d%d",&o,&lx,&ly,&rx,&ry);
if(o==1){
if(ly==ry&&rx>=lx&&Y[rx][ly]-Y[lx-1][ly]==0)ans[i]=1;
}
else if(o==2){
if(lx==rx&&ry>=ly&&X[lx][ry]-X[lx][ly-1]==0)ans[i]=1;
}
else if(lx<=rx&&ly<=ry)p[lx].eb(P{rx,ly,ry,i});
}
for(int l=1;l<=n;++l){
//将当前行上每个起点出发的询问,存储到终点的每行中
for(auto o:p[l])t[o.rx].eb(o);
//初始化
//vis[x][y] 表示 (l,y) 能否走到 (?,x)
for(int i=1;i<=m;++i)vis[i].reset();
for(int i=1;i<=m;++i){
//若(l,i)有障碍物,清零
if(c[l][i]=='1')vis[i].reset();
//(l,i)无障碍物,标记(l,i) 可以走到 (l,i) ,合并上能走到 (l,i-1) 的起点方案
else vis[i][i]=1,vis[i]|=vis[i-1];
}
//枚举行
for(int r=l;r<=n;++r){
for(int i=1;i<=m;++i){
//走到 (r,i) 的方案,要合并上能走到 (r,i-1) 的方案
vis[i]|=vis[i-1];
//若 (r,i) 存在障碍物,则无方案
if(c[r][i]=='1')vis[i].reset();
}
for(auto o:t[r]){
ans[o.id]=vis[o.ry][o.ly];
}
t[r].clear();
}
}
for(int i=1;i<=q;++i){
ans[i]?puts("yes"):puts("no");
}
return 0;
}
//压前两个维度
#include <bits/stdc++.h>
using namespace std;
const int N = 505;
int n, m, q;
char mmap[N][N];
bool ans[500010];
bitset<N * N> bs[N];
struct node {
int id, t, x, y;
};
vector<node> v[N][N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%s", mmap[i] + 1);
scanf("%d", &q);
for (int i = 0; i < q; i++) {
int t, x1, y1, x2, y2;
scanf("%d%d%d%d%d", &t, &x1, &y1, &x2, &y2);
v[x1][y1].push_back({i, t, x2, y2});
}
for (int i = n; i; i--)
for (int j = m; j; j--) {
if (mmap[i][j] == '0') {
if (mmap[i][j + 1] == '0') bs[j] |= bs[j + 1];
bs[j][i * m + j] = 1;
}
else bs[j].reset();
for (node &no : v[i][j]) {
if (no.t == 1)
ans[no.id] = no.y == j && bs[j][no.x * m + no.y];
else if (no.t == 2)
ans[no.id] = no.x == i && bs[j][no.x * m + no.y];
else
ans[no.id] = bs[j][no.x * m + no.y];
}
}
for (int i = 0; i < q; i++) puts(ans[i] ? "yes" : "no");
return 0;
}
// https://ac.nowcoder.com/acm/contest/view-submission?submissionId=48490864
总结
bitset可用于优化代码减小维度,可以针对较大一点的数据,还是有必要去学习掌握的。
(说不定就来考了)