P1162 填涂颜色
P1162 填涂颜色
大概思路,暴搜所有\(0\)点,搜到就填色,然后搜色块的时候一旦碰边就打标记,回溯之后看标记决定标\(0\)还是标\(2\)。
思路是理论可行,但是代码容易出问题。
一开始\(48pts\)的代码
#include<iostream>
#include<algorithm>
#include<cstdio>
const int N = 31;
int n;
int map[N][N], vis[N][N];
bool is_circle;
int walk[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
using namespace std;
void dfs(int x, int y)
{
if (x == 1 || y == 1 || x == n || y == n) is_circle = false;
for (int i = 0; i <= 3; i++)
{
int dx = x + walk[i][0], dy = y + walk[i][1];
if (dx < 1 || dx > n || dy < 1 || dy > n || map[dx][dy] == 1) continue;
if (is_circle && vis[dx][dy] > 1) continue;
if (!is_circle && vis[dx][dy]) continue;
vis[dx][dy]++;
dfs(dx, dy);
if (is_circle) vis[dx][dy]++;
}
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
scanf("%d", &map[i][j]);
}
}
is_circle = true;
dfs(3, 4);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (!map[i][j] && !vis[i][j])
{
is_circle = true;
vis[i][j]++;
dfs(i, j);
if (is_circle) vis[i][j]++;
}
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if(map[i][j] == 1)
{
printf("1 ");
} else if(vis[i][j] > 1){
printf("%d ", vis[i][j]);
} else {
printf("0 ");
}
}
printf("\n");
}
return 0;
}
对照数据发现有一些点\(vis[i][j] > 2\)了,才意识到if (is_circle) vis[dx][dy]++;
这是不严谨的,具体原因还没法描述出来,但是修改思路还是很显然,我改成了if (is_circle) vis[dx][dy] = 2;
,再次提交,还是有一些点没过。
再对照一些数据点,发现一些本不可以被包围的圈内出现了零散的\(2\),意识到这个算法存在不严谨:
假设一次搜索进入的色块是没有被围住的
那么第一次进入搜索直到第一次触边前,
is_circle == true
,如此一来,直到第一次触边前所有的
vis[x][y]
都被理所当然的赋为了\(2\).
知道这个问题之后,要解决就简单了,递归返回到本次函数后,再专门对is_circle
进行判断,如果变成了\(false\), 就把所有标记为\(2\)的vis[x][y]
还原为\(1\)。
AC code
#include<iostream>
#include<algorithm>
#include<cstdio>
const int N = 31;
int n;
int map[N][N], vis[N][N];
bool is_circle;
int walk[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
using namespace std;
void dfs(int x, int y)
{
if (x == 1 || y == 1 || x == n || y == n) is_circle = false;
for (int i = 0; i <= 3; i++)
{
int dx = x + walk[i][0], dy = y + walk[i][1];
if (dx < 1 || dx > n || dy < 1 || dy > n || map[dx][dy] == 1) continue;
if (is_circle && vis[dx][dy] > 1) continue;
if (!is_circle && vis[dx][dy]) continue;
vis[dx][dy]++;
dfs(dx, dy);
if (is_circle)
vis[dx][dy] = 2;
}
if (!is_circle)
{
for (int i = 0; i <= 3; i++)
{
int dx = x + walk[i][0], dy = y + walk[i][1];
if (dx < 1 || dx > n || dy < 1 || dy > n || map[dx][dy] == 1) continue;
vis[dx][dy] = 1;
}
}
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
scanf("%d", &map[i][j]);
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (!map[i][j] && !vis[i][j])
{
is_circle = true;
vis[i][j]++;
dfs(i, j);
if (is_circle) vis[i][j] = 2;
}
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if(map[i][j] == 1)
{
printf("1 ");
} else if(vis[i][j] > 1){
printf("%d ", vis[i][j]);
} else {
printf("0 ");
}
}
printf("\n");
}
return 0;
}
虽然如此,但显然这个做法是不优秀的,发现题解中有一个很妙的方法。
先看代码
#include <bits/stdc++.h>
using namespace std;
int a[32][32],b[32][32];
int dx[5]={0,-1,1,0,0};
int dy[5]={0,0,0,-1,1};//第一个表示不动,是充数的,后面的四个分别是上下左右四个方向
int n,i,j;
void dfs(int p,int q){
int i;
if (p<0||p>n+1||q<0||q>n+1||a[p][q]!=0) return;//如果搜过头或者已经被搜过了或者本来就是墙的就往回
a[p][q]=1;//染色
for (i=1;i<=4;i++) dfs(p+dx[i],q+dy[i]);//向四个方向搜索
}
int main(){
cin>>n;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++){
cin>>b[i][j];//其实不拿两个数组也可以,不过我喜欢啦
if (b[i][j]==0) a[i][j]=0;
else a[i][j]=2;
}
dfs(0,0);//搜索 从0,0开始搜
for (i=1;i<=n;i++){
for (j=1;j<=n;j++)
if (a[i][j]==0) cout<<2<<' ';//如果染过色以后i,j那个地方还是0,说明没有搜到,就是周围有墙,当然就是被围住了,然后输出2
else cout<<b[i][j]<<' ';//因为被染色了,本来没有被围住的水和墙都染成了1,所以就输出b[i][j]
cout<<'\n';//换行
}
}
这个代码有两个关键思路
- 逆向思维
- 只从外层进行搜索染色,那么所有没有被染色到的显然就是被墙围住的。
- 构造外圈
- 如果用for循环枚举外层的\(0\)进行搜索则不够优雅
- 直接把存地图时存\(1 \sim N\)然后在\(0, n+1\)构造外层,等于说用一个大边围住了整个地图,而这个边是可通过的,因此只要从\((0,0)\)开始搜就行,不用找其他外层点。
妙哉。