题意:
有一个4*4的矩阵,01填充,通过翻转把所有的0变为1,但是翻转[i,j]的同时,要把同行同列的都翻转。
思路:
最开始的时候写了裸的BFS,太过暴力所以果断TLE。看了题解才知道是状态压缩,很高兴能遇到这样经典的状态压缩题目。虽然有巧妙的解法,但是从状态压缩中学到了更多的东西。
首先有几个点要先明白:
1. 每个点最多翻转一次;(翻转2n次相当于没翻转,翻转2n+1次相当于翻转1次)
2. 每个点翻转的顺序无所谓,组合起来的结果相同;
3. unsigned short(16 bits)可以存储一个情况;
4. 最多有2^16=65536种组合情况;
5. 状态的转换可以用位运算完成;
这样我们可以用一个unsigned short型的变量来存储状态,另外用一个结构体数组来存储状态的前驱和变换次数,两个数组都只需要开到65536,这样我们再进行BFS效率就会高很多了。
代码实现:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX = 65536;
struct Node{
bool exist;
int pre;
int change;
int times;
};
int pos;
int num;
Node que[MAX];
int dx[16] = {1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4};
int dy[16] = {1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4};
unsigned short lis[MAX];
unsigned short update_pos(unsigned short cur,int type);
void check(unsigned short cur);
int main(){
char tmp;
int cnt = 0;
unsigned short initial = 0;
for(int i = 0;i < 4; i++ ){
for( int j = 0; j < 4; j++ ){
scanf("%c",&tmp);
if( tmp == '-' ){
initial |= (1<<cnt);
}
cnt++;
}
getchar();
}
for( int i = 0; i < MAX; i++ ){
que[i].change = -1;
que[i].exist = false;
que[i].pre = -1;
que[i].times = 0;
}
que[initial].exist = true;
pos = 0;
num = 1;
lis[0] = initial;
//check(initial);
while( pos < num ){
unsigned short cur = lis[pos];
for( int i = 0; i < 16; i++ ){
unsigned short rep = update_pos(cur,i);
//check(rep);
if( que[rep].exist == true ){
continue;
}
que[rep].exist = true;
que[rep].pre = cur;
que[rep].change = i;
que[rep].times = que[cur].times+1;
lis[num] = rep;
num++;
//结果可以按任意顺序输出
if( rep == 0xffff ){
printf("%d\n",que[rep].times);
while( que[rep].pre != -1 ){
printf("%d %d\n",dx[que[rep].change],dy[que[rep].change]);
rep = que[rep].pre;
}
return 0;
}
}
pos++;
}
return 0;
}
unsigned short update_pos(unsigned short cur,int type){
unsigned short mod;
switch(type){
case 0:
mod = 0x111f;break;
case 1:
mod = 0x222f;break;
case 2:
mod = 0x444f;break;
case 3:
mod = 0x888f;break;
case 4:
mod = 0x11f1;break;
case 5:
mod = 0x22f2;break;
case 6:
mod = 0x44f4;break;
case 7:
mod = 0x88f8;break;
case 8:
mod = 0x1f11;break;
case 9:
mod = 0x2f22;break;
case 10:
mod = 0x4f44;break;
case 11:
mod = 0x8f88;break;
case 12:
mod = 0xf111;break;
case 13:
mod = 0xf222;break;
case 14:
mod = 0xf444;break;
case 15:
mod = 0xf888;break;
}
return cur^mod;
}
void check(unsigned short cur){
for( int i = 0; i < 4; i++ ){
for( int j = 0; j < 4; j++ ){
printf("%d ",cur%2);
cur /= 2;
}
printf("\n");
}
printf("\n");
}
另外的一种巧妙解法,也很好理解:
每一个+都可以通过7次翻转变为-(中间加同行同列每个格子转一次),同时同行同列的其余六个格子(变换4次)不改变,其余格子(变换2次)也不改变。那么我们可以按照这个方法操作每一个关闭的开关'+',想要得到最少就是要去掉不必要的翻转,而不必要的翻转就是那些操作了偶数次的点(因为一个点翻转了偶数次的效果和不翻转效果相同)。之后把那些统计操作次数为奇数次的点按顺序操作一次就好了(因为一个点操作奇数次和操作1次的效果相同,且顺序任意)。
代码实现:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
int res;
int cnt[16];
int main(){
res = 0;
char tmp;
memset(cnt,0,sizeof(cnt));
for( int i = 0; i < 16; i++ ){
cin>>tmp;
if( tmp == '+' ){
int row = i/4;
int column = i%4;
cnt[row*4]++;
cnt[row*4+1]++;
cnt[row*4+2]++;
cnt[row*4+3]++;
cnt[column]++;
cnt[column+4]++;
cnt[column+8]++;
cnt[column+12]++;
cnt[row*4+column]--;
}
}
for( int i = 0; i < 16; i++ ){
if( cnt[i]%2 == 1 ){
res++;
}
}
printf("%d\n",res);
for( int i = 0; i < 16; i++ ){
if( cnt[i]%2 == 1 ){
printf("%d %d\n",i/4+1,i%4+1);
}
}
return 0;
}