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)\)开始搜就行,不用找其他外层点。

妙哉。

posted @ 2023-10-30 09:43  加固文明幻景  阅读(13)  评论(0编辑  收藏  举报