思路:每次涂色以后必有一个格子的颜色是最终的颜色,否则这次涂色根本没意义,所以我们把每次涂色后哪些格子成为最终颜色的所有可能都入队,入队的元素是一个struct包含步数和最终颜色已确定的木块集合,这个集合必须用整数表示,所以用到状态压缩,因为最多只有16个格子,所以用16位的二进制来表示,1,表示此格子已是最终颜色,0,表示仍待涂色。
这道题目确实很难想,我最开始想的是用字符串哈希把每次涂的情况存下来,结果挂了。。。其实这样的做法并不能保证就一定是最少的步骤,因为涂的情况不同,但最终都能够到达最终的状态。。所以并不见得就一定是最优的。。
这题至少让我知道了如何找位压缩后的子集。。。详见代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
struct node
{
int step,cur;
};
int len;
bool flag[1<<16]; // 记录哪些格子已是最终颜色
char str[20];
int bfs()
{
memset(flag,0,sizeof(flag));
queue<node> que;
node start={0,0};
que.push(start);
flag[0]=true;
while(!que.empty()){
node now=que.front();
que.pop();
if(now.cur==(1<<2*len)-1) return now.step; //颜色已涂满所有格子
node next;
next.step=now.step + 1;
for(int i = 0;i < 2*len; i++){ //遍历这个图,找个起点开始涂色
int tmp=0;
if((1<<i) & now.cur) continue; //这个点已是最终颜色,continue
for(int j = i;j < (i/len+1)*len; j++){ //单行向右扩展,上界的确定是技巧
if((1<<j)&now.cur) break; //已扩展到最终颜色,不能把它覆盖,扩展结束
if(str[j]==str[i]) tmp|=(1<<j); //如果拓展的位置需涂的颜色和起点颜色一样,那么就涂
}
for(int j = i-1;j >= (i/len)*len; j--){ //单行向左扩展,下界的确定是技巧
if((1<<j) & now.cur) break; //已扩展到最终颜色,不能把它覆盖,扩展结束
if(str[j] == str[i]) tmp|=(1<<j);//如果拓展的位置需涂的颜色和起点颜色一样,那么就涂
}
for(int j = tmp; j > 0; j = tmp & (j-1)){
//把本次单行扩展的所有格子最终哪些格子成为最终颜色的所有可能入队,所有
//可能也就是子集的所有可能,这种遍历集合子集的方式属于位压缩的知识
if(!flag[j|now.cur]){ //这种情况已经产生过就不需入队了
flag[j|now.cur]=1;
next.cur=(j|now.cur);
que.push(next);
}
}
if(i >= len) continue; //双行扩展,只要对某一行的点遍历作为起点即可
if((1<<(i+len)) & now.cur) continue; //易错,这个起点的另一行对应的起点如果已是最终颜色,continue
tmp=0;
for(int j = i;j < len; j++){ //和单行拓展类似
if((1<<j) & now.cur)break;
if((1<<(j+len)) & now.cur)break;
if(str[j] == str[i]) tmp|=(1<<j);
if(str[j+len] == str[i]) tmp|=(1<<(j+len));
}
for(int j = i-1; j >= 0; j--){
if((1<<j) & now.cur) break;
if((1<<(j+len)) & now.cur) break;
if(str[j] == str[i]) tmp|=(1<<j);
if(str[j+len] == str[i]) tmp|=(1<<(j+len));
}
for(int j = tmp; j > 0; j = tmp & (j-1)){ //和单行拓展子集入队类似
if(!flag[j|now.cur]){
flag[j|now.cur]=1;
next.cur=(j|now.cur);
que.push(next);
}
}
}
}
}
int main()
{
int t,cas = 1;
scanf("%d",&t);
while(t--){
scanf("%d",&len);
scanf("%s%s",str,str+len);
printf("Case #%d: %d\n",cas++,bfs());
}
return 0;
}