Description
地毯上的格子有N行N列,每个格子用一个0~5之间的数字代表它的颜色。
你可以随意选择一个0~5之间的颜色,然后按下按钮,左上角的格子所在的联通块里的所有格子就会变成你选择的那种颜色。这里连通定义为:两个格子有公共边,并且颜色相同。
你想知道最少要按多少次按钮才能把所有格子的颜色变成一样的。
Input
每个测试点包含多组数据。
每组数据的第一行是一个整数N,表示地摊上的格子有N行N列。
接下来一个N*N的矩阵,矩阵中的每个数都在0~5之间,描述了每个格子的颜色。
N=0代表输入的结束。
Output
对于每组数据,输出一个整数,表示最少步数。
Sample Input
2
0 0
0 0
3
0 1 2
1 1 2
2 2 1
0
Sample Output
0
3
Hint
对于30%的数据,N<=5
对于50%的数据,N<=6
对于70%的数据,N<=7
对于100%的数据,N<=8,每个测试点不多于20组数据。
【分析】
官方题解:
类型:IDA* (迭代加深启发式搜索)
方法一:
枚举每次选取了哪种颜色,然后找出左上角的格子所在的联通块,改变颜色。
为了避免来回改变、搜索深度过大,采用迭代加深的dfs限制搜索步数。
迭代加深也就是,依次限制搜索深度为0、1、2、3…… 进行搜索,搜索过程中发现深度超过限制就马上退出。只要搜索成功就 找到了答案,也可以立即退出。
期望得分:0~10分。
方法二:
加入一个小剪枝:如果改变颜色后,左上角格子所在的联通块大小没有改变,可以剪枝。这样可以避免来回往复地搜索。
期望得分:10~20分。
方法三:
采用IDA*算法,设计估价函数。可以发现如果当前矩阵中除了左上角的联通块之外,共有M种颜色,那么还需要的步数不小于 M。因此如果当前搜索深度+估价函数的值>深度限制,可以回溯。
期望得分:50~70分。
方法四:
我们可以发现,每次寻找左上角的格子所在的联通块耗费的时间常数巨大。因此我们在这里寻求突破。我们引入一个N*N的v数 组。左上角的格子所在的联通块里的格子标记为1。左上角联通块周围一圈格子标记为2,其它格子标记为0。如果某次选择了颜色 c,我们只需要找出标记为2并且颜色为c的格子,向四周扩展,并相应地修改v标记,就可以不断扩大标记为1的区域,最终如果所 有格子标记都是1,那么显然找到了答案。
期望得分:90~100分。
【代码】
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;
const int dx[4]={-1,0,0,1},dy[4]={0,-1,1,0};
int a[9][9],v[9][9],f[6],n,ID;
int left() //计算图中剩下的颜色数
{
int i,j,temp=0;
memset(f,0,sizeof(f));
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(!f[a[i][j]]&&v[i][j]!=1)
{f[a[i][j]]=1; temp++;}
return temp;
}
void dfs(int x,int y,int c)//标记连通块
{
v[x][y]=1;
for(int i=0;i<4;i++)
{
int nx=x+dx[i],ny=y+dy[i];
if(nx<1||ny<1||nx>n||ny>n||v[nx][ny]==1)
continue;
v[nx][ny]=2;
if(a[nx][ny]==c)dfs(nx,ny,c);
}
}
int fill(int c) //染色
{
int temp=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j]==c&&v[i][j]==2)
{
++temp;
dfs(i,j,c);
}
return temp;
}
bool IDAstar(int dep) //迭代搜索
{
int g=left();
if(dep+g>ID)return 0;
if(!g)return 1;
int rec[9][9];
for(int i=0;i<=5;i++)
{
memcpy(rec,v,sizeof(v));
if(fill(i)&&IDAstar(dep+1))return 1;
memcpy(v,rec,sizeof(v));
}
return 0;
}
int main()
{
while(cin>>n&&n)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
memset(v,0,sizeof(v));
dfs(1,1,a[1][1]);
for(ID=0;;ID++)
if(IDAstar(0))
break;
cout<<ID<<endl;
}
return 0;
}