背景 Background
水叮当得到了一块五颜六色的格子形地毯作为生日礼物,更加特别的是,地毯上格子的颜色还能随着踩踏而改变。
为了讨好她的偶像虹猫,水叮当决定在地毯上跳一支轻盈的舞来卖萌~~~
描述 Description
地毯上的格子有N行N列,每个格子用一个0~5之间的数字代表它的颜色。
水叮当可以随意选择一个0~5之间的颜色,然后轻轻地跳动一步,左上角的格子所在的联通块里的所有格子就会变成她选择的那种颜色。这里连通定义为:两个格子有公共边,并且颜色相同。
由于水叮当是施展轻功来跳舞的,为了不消耗过多的真气,她想知道最少要多少步才能把所有格子的颜色变成一样的。
输入格式 InputFormat
每个测试点包含多组数据。
每组数据的第一行是一个整数N,表示地摊上的格子有N行N列。
接下来一个N*N的矩阵,矩阵中的每个数都在0~5之间,描述了每个格子的颜色。
N=0代表输入的结束。
输出格式 OutputFormat
对于每组数据,输出一个整数,表示最少步数。
样例输入 SampleInput
2
0 0
0 0
3
0 1 2
1 1 2
2 2 1
0
样例输出 SampleOutput
0
3
数据范围和注释 Hint
对于30%的数据,N<=5
对于50%的数据,N<=6
对于70%的数据,N<=7
对于100%的数据,N<=8,每个测试点不多于20组数据。
这题首先告诉我们,一定要审题,仔细审题,用心审题。
一开始看这道题,以为好难,要怎么怎么复杂地广搜。然后经过杨之典大触的指出后,我才知道其实每次只是动左上角那个联通块,于是问题就清晰了。
我们经过一定的思考,这题广搜其实没有深搜好用,于是我们就用深搜来解决这道题。
那么用深搜就一定要有一定的剪枝,所以我们来看看怎么剪枝。
首先第一个剪枝很明显,如果在ans步内已经搜到过目标,那么之后搜索的深度肯定在这个范围之内。
然后是第二个剪枝。假设我们现在图中有a中颜色,现在是b步,已经在ans步内搜到过答案,那么如果a+b>ans的话也没必要往下搜了,因为每次最多改变一种颜色。
第三个剪枝,假设我们左上角联通块周围的颜色是2,4,那么我们这时还要不要把左上角的联通块变成五个颜色往下搜呢?显然,我们只要把左上角的联通块变成2或4再往下搜,
变成其他颜色显然不是最优的。
第四个剪枝,是建设在第三个剪枝之上的,假设我们左上角的联通块周围的颜色是2,4,而且2有4个,4只有2个,那个我们先搜哪个呢?当然是先搜2,。
讲了这么多,感觉好麻烦,其实,真的很麻烦。
代码:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define ll long long #define il inline #define db double using namespace std; il int gi() { int x=0,y=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘) { if(ch==‘-‘) y=-1; ch=getchar(); } while(ch>=‘0‘&&ch<=‘9‘) { x=x*10+ch-‘0‘; ch=getchar(); } return x*y; } il ll gl() { ll x=0,y=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘) { if(ch==‘-‘) y=-1; ch=getchar(); } while(ch>=‘0‘&&ch<=‘9‘) { x=x*10+ch-‘0‘; ch=getchar(); } return x*y; } int n; int ans; int map[10][10]; int pre[10][10]; bool v[10][10]; il bool check()//检查是否满足条件 { int now=map[1][1]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(map[i][j]!=now) return 0; return 1; } il void hzr()//记录修改前的图 { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) pre[i][j]=map[i][j]; } il void hzrr()//用来回溯 { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) map[i][j]=pre[i][j]; } il int count()//用来做第二个剪枝的函数 { int sum=0; bool vis[45]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(!vis[map[i][j]]) { sum++; vis[map[i][j]]=1; } return sum; } struct col { int co,s; }c[10];//用来记录联通块周围的颜色种类和数量的结构体 bool cmp(col a,col b) { return a.s>b.s; } bool vis[10][10]; il void bfs()//bfs求联通块,并且记录左上角的联通块周围的颜色 { int head=0,tail=1; int t[100][2];//用来广搜的队列 for(int i=0;i<=5;i++) c[i].s=0,c[i].co=i;//初始化记录的结构体 memset(vis,0,sizeof(vis)); int dis[5]={0,1,0,-1,0}; t[0][0]=1; t[0][1]=1; v[1][1]=1; vis[1][1]=1; while(head!=tail) { for(int i=0;i<4;i++) { int x=t[head][0]+dis[i],y=t[head][1]+dis[i+1]; if(vis[x][y]||x>n||x<1||y>n||y<1)//如果在界外或者已是联通块里的 continue; if(map[x][y]!=map[1][1])//如果是周围的不同颜色的就记录 { c[map[x][y]].s++; continue; } vis[x][y]=1; t[tail][0]=x; t[tail++][1]=y; } head++; } } il void change(int x)//将联通块变成目标颜色 { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(vis[i][j]) map[i][j]=x; } viod dfs(int x) { if(x>ans)//第一个剪枝 return; if(check())//如果满足条件,那么就记录 { if(ans>x) ans=x; return; } if(x+count()>ans)//第二个剪枝 return; hzr();//为回溯做准备 bfs();//求出联通块,并且记录联通块周围的颜色种类和数量 sort(c,c+5,cmp);//按周围颜色出现的次数排序 for(int i=0;i<=5;i++) { if(!c[i].s) break; change(c[i].co);//把联通块都变成目标颜色 dfs(x+1);//往下搜 hzrr();//回溯 } } int main() { while(1) { n=gi(); if(!n) break; ans=2e8; memset(map,0,sizeof(map)); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) map[i][j]=gi(); dfs(0); printf("%d\n",ans); } return 0; }