2021牛客暑期多校训练营第8场 F题(两种做法)
2021牛客暑期多校训练营8 F题(两种做法)
链接:https://ac.nowcoder.com/acm/contest/11259/F
来源:牛客网
题意:
有一个 \(n*m\) 的货运中心,每个格子为0或1,1代表有障碍不可通行。
有3种机器人,一种只能向右走,一种只能向下走,一种可以向下或向右走。
现在有 \(Q\) 个询问,每个询问包含了机器人型号,一个起点坐标和一个终点坐标,请问机器人能否将货物从起点运送到终点
输入
4 4
0000
0000
0001
0010
4
1 1 1 1 4
2 2 1 2 4
3 1 1 3 3
3 3 3 4 4
输出
no
yes
yes
no
补题:
我首先花了一晚上去看某十字的代码,理解了一半
另一半感觉像是某种玄学的按64位分块后再状压,不是很理解,不过这部分可以用bitset搞过去
从右下往左上枚举,设 \(f[i][j][k]\)表示枚举到当前位置时(注意 \(i\) 不是当前行,\(j\) 是当前列),能否走到 \((i,k)\) 这个位置
第三维 \(k\) 是可以被压掉的,把 \(f[i][j]\) 设为bitset,那么其实有 \(f[i][j] = f[i][j] \quad or\quad f[i][j+1]\)
这个状态很玄妙。。。可以结合代码手动跑感受一下
看不懂也没事,标答的分治做法更好理解(也更快)
bitset<502> f[505][505];
int mp[505][505], ans[500015];
int dw[505][505], rt[505][505];
struct ques{
int x2,y2,id;
};
vector<ques> ot[505][505];
void solve(){
int n,m,Q;
cin >> n >> m;
for(int i=1;i<=n;i++){
string s;
cin >> s;
for(int j=0;j<m;j++){
mp[i][j+1] = s[j] - '0';
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
rt[i][j] = rt[i][j-1] + mp[i][j];
dw[i][j] = dw[i-1][j] + mp[i][j];
}
}
cin >> Q;
for(int i=1;i<=Q;i++){
int tp, x1, y1 ,x2 ,y2;
cin >> tp >> x1 >> y1 >> x2 >> y2;
if(x2 < x1 || y2 < y1) ans[i] = 0;
else if(tp==1){
ans[i] = (y1 == y2 && dw[x1][y1] == dw[x2][y2]);
}else if(tp==2){
ans[i] = (x1 == x2 && rt[x1][y1] == rt[x2][y2]);
}else{
ot[x1][y1].push_back({x2, y2, i});
}
}
for(int i=0;i<=n+2;i++) for(int j=0;j<=m+2;j++) f[i][j] = 0;
for(int i=n;i>=1;i--){
for(int j=m;j>=1;j--){
if(mp[i][j]){
for(int k=n;k>=i;k--){
f[k][j] = 0;
}
}else{
f[i][j][j] = true;
if(j!=m) for(int k=n;k>=i;k--){
f[k][j] |= f[k][j+1];
}
}
for(int k=0;k<ot[i][j].size();k++){
int xv = ot[i][j][k].x2, yv = ot[i][j][k].y2;
ans[ot[i][j][k].id] = f[xv][j][yv];
}
}
}
for(int i=1;i<=Q;i++){
if(ans[i]) cout << "yes\n";
else cout << "no\n";
}
}
标答做法(分治):
对整个矩阵竖着来一刀,划分成 \([l, mid]\), \([mid+1, r]\)
考虑处理横跨左右的询问,以 \(mid\) 这一列为枢纽
对所有左边的点,处理出从它开始能走到中线上哪些位置
对所有右边的点,处理出中线上哪些点开始能走到这个它
显然任何横跨两边的询问,如果可行那么它们必然存在一个枢纽
其他询问分治做就可以啦
int n,m,Q;
bitset<502> f[505][505];
int mp[505][505], ans[500015];
int dw[505][505], rt[505][505];
struct ques{
int x2,y2,id;
};
vector<ques> ot[505][505];
void CDQ(int l, int r){
if(l==r){//边界处理
for(int i=n;i>=1;i--){
if(mp[i][l]) f[i][l] = 0;
else{
f[i][l][i] = 1;
f[i][l] |= f[i+1][l];
}
for(int k=0;k<ot[i][l].size();k++){
int xv = ot[i][l][k].x2, yv = ot[i][l][k].y2;
if(l == yv && xv >= i){
ans[ot[i][l][k].id] = f[i][l][xv];
}
}
}
for(int i=1;i<=n;i++) f[i][l] = 0;
return;
}
int mid = (l+r)/2;
CDQ(l, mid);
CDQ(mid+1, r);
for(int i=1;i<=n;i++){//计算右半边每个点能由哪些中点到达
if(mp[i][mid]) f[i][mid] = 0;
else{
f[i][mid][i] = 1;
f[i][mid] |= f[i-1][mid];
}
}
for(int i=1;i<=n;i++){
for(int j=mid+1;j<=r;j++){
if(mp[i][j]) f[i][j] = 0;
else{
f[i][j] |= f[i-1][j];
f[i][j] |= f[i][j-1];
}
}
}
for(int i=1;i<=n;i++) f[i][mid] = 0;
for(int i=n;i>=1;i--){//计算左半边每个点能到达哪些中点
if(mp[i][mid]) f[i][mid] = 0;
else{
f[i][mid][i] = 1;
f[i][mid] |= f[i+1][mid];
}
}
for(int i=n;i>=1;i--){
for(int j=mid-1;j>=l;j--){
if(mp[i][j]) f[i][j] = 0;
else{
f[i][j] |= f[i+1][j];
f[i][j] |= f[i][j+1];
}
}
}
for(int i=1;i<=n;i++){//处理答案
for(int j=l;j<=mid;j++){
for(int k=0;k<ot[i][j].size();k++){
int xv = ot[i][j][k].x2, yv = ot[i][j][k].y2;
if(mid < yv && yv <= r){
ans[ot[i][j][k].id] = (f[i][j] & f[xv][yv]).count();
}
}
}
}
for(int i=1;i<=n;i++) for(int j=l;j<=r;j++) f[i][j] = 0;//撤销
}
void solve(){
cin >> n >> m;
for(int i=1;i<=n;i++){
string s;
cin >> s;
for(int j=0;j<m;j++){
mp[i][j+1] = s[j] - '0';
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
rt[i][j] = rt[i][j-1] + mp[i][j];
dw[i][j] = dw[i-1][j] + mp[i][j];
}
}
cin >> Q;
for(int i=1;i<=Q;i++){
int tp, x1, y1 ,x2 ,y2;
cin >> tp >> x1 >> y1 >> x2 >> y2;
if(x2 < x1 || y2 < y1) ans[i] = 0;
else if(tp==1){
ans[i] = (y1 == y2 && dw[x1][y1] == dw[x2][y2]);
}else if(tp==2){
ans[i] = (x1 == x2 && rt[x1][y1] == rt[x2][y2]);
}else{
ot[x1][y1].push_back({x2, y2, i});
}
}
CDQ(1, m);
for(int i=1;i<=Q;i++){
if(ans[i]) cout << "yes\n";
else cout << "no\n";
}
}