POJ 1753 Flip Game
零、首先数据n=16,完全可以暴搜。
2^20=1048576,一秒千万次操作,n<=20的数据都可以暴搜。
仔细想一下,现在用的是枚举不是暴搜……
一、其实思路不难:
1.每一个位置只有翻奇数次和翻偶数次两种情况,而且偶数次相当于不翻,所以仅有两种情况,翻或不翻。
2.翻的顺序对结果没有影响。
3.翻所有的白还是所有的黑?如何翻?——暴搜事实上是不需要考虑这个问题的——翻所有16个格,直到出现全白或者全黑。
二、思路:
生成16全排列,01分别代表翻或不翻该位,如果某种排列方式达到结果状态,则更新最小状态,最后输出结果。
中间最关键的一步就是生成子集。
注:事实上,对数组应该是生成子集(翻的位置集合),对位运算是生成全排列(所有位置的状态的全排列)。
生成集合的子集就是生成位运算全排列。
方法一:数组+递归
暴搜复杂度分析:因为只有十六个格子,每个格子有翻和不翻这两种情况,所以你哪怕枚举每一种情况,总的枚举次数也不过是2^16=65536次,所以在时间复杂度上是不会产生问题的。
递归模板:
Digui(){
If (递归到最后一层即递归边界) {判断最终数据是否满足标准状态};
Else{
For(循环次数为该层分支数){
处理该分支;
Digui(递归到下一层);
} ·
}//else
}//digui
1 /* 2 POJ 1753 Flip Game (递归枚举) 3 By Microgoogle 4 */ 5 #include <stdio.h> 6 #include <stdlib.h> 7 8 //判断是否达到结果状态:所有都是白的,或者所有都是黑的 9 int all_white_or_black(int* bits, int len) 10 { 11 int i = 0; 12 for (i = 0; i < len - 1; i++) 13 if (bits[i] != bits[i + 1]) 14 return 0; 15 return 1; 16 } 17 18 //改变一个格子的颜色,并根据其所在位置改变其周围格子的颜色 19 void change_color(int* arr, int i) 20 { 21 arr[i] = !(arr[i]); 22 int x = i/4; 23 int y = i%4; 24 if (y < 3) 25 arr[i + 1] = !(arr[i + 1]); 26 if (y > 0) 27 arr[i - 1] = !(arr[i - 1]); 28 if (x > 0) 29 arr[i - 4] = !(arr[i - 4]); 30 if (x < 3) 31 arr[i + 4] = !(arr[i + 4]); 32 } 33 34 //递归生成子集 35 //这个完全用了前一篇文章的递归方法,只是在else语句中添加了整个图形是否为纯色的判断而已 36 void combine(int* arr, int len, int* result, int count, const int NUM, int* last) 37 { 38 int i; 39 for (i = len; i >= count; i--)//对子集元素数循环 40 { 41 result[count - 1] = i - 1; 42 if (count > 1) 43 combine(arr, i - 1, result, count - 1, NUM, last); 44 else 45 { 46 int j = 0; 47 48 //在这里生成arr的副本 49 int* new_arr = (int*)malloc(sizeof(int)*16); 50 for (j = 0; j < 16; j++) 51 new_arr[j] = arr[j]; 52 53 for (j = NUM - 1; j >=0; j--){ 54 change_color(new_arr, result[j]); 55 }//for j 56 if (all_white_or_black(new_arr, 16)) { 57 *last = NUM; 58 free(new_arr); 59 break; 60 }//if 61 free(new_arr); 62 }//else 63 }//for i 64 }//combine 65 66 int main() 67 { 68 char str[5]; 69 int bits[16]; 70 int count = 15; 71 int lines = 4; 72 73 //读入 74 while (lines--) 75 { 76 scanf("%s", str); 77 int i; 78 for (i = 0; i < 4; i++) 79 { 80 if (str[i] == 'b') 81 bits[count--] = 1; 82 else 83 bits[count--] = 0; 84 } 85 } 86 87 if (all_white_or_black(bits, 16)) 88 printf("%d\n", 0); 89 else 90 { 91 //生成bits数组的副本 92 int* new_bits = (int*)malloc(sizeof(int)*16); 93 int i; 94 for (i = 0; i < 16; i++) 95 new_bits[i] = bits[i]; 96 int j; 97 //这里last用来接受combine函数里面的NUM,即需要的步数 98 int last = 0; 99 for (j = 1; j <= 16; j++){ 100 int* result = (int*)malloc(sizeof(int)*j); 101 combine(new_bits, 16, result, j, j, &last); 102 if (last == j){ 103 printf("%d\n", last); 104 break; 105 }//if 106 //new_bits已被改变,所以要还原为bits 107 for (i = 0; i < 16; i++) 108 new_bits[i] = bits[i]; 109 110 free(result); 111 }//for j 112 free(new_bits); 113 114 if (j == 17) printf("Impossible\n"); 115 }// 16 116 117 return 0; 118 } 119 120 121 写的不好,生成子集那步写复杂了。
方法二:位运算+简单循环 //位运算:freedom\个人经验\位运算及用例
位运算:
存储这个字符数组的时候利用一个value值来存储,对value进行位运算操作,枚举每一种翻与不翻的情况,记录每一次使得value == 0||value == 65535的值,得出最后步数最小的结果。
相比数组的方法,位运算最大的优势并不在于空间复杂度上,而是简单操作速度快并避免递归生成全排列。
1 //1753 2 #include <iostream> 3 #define MAX 999999 4 using namespace std; 5 char s[4][4]; 6 int cs[16] = {0x13,0x27,78,140,305,626,1252,2248,4880,8992,20032,35968,12544,29184,58368,51200}; 7 int po[16] = {1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768}; 8 int main() 9 { 10 int i,j,value = 0; 11 int cmin = MAX; 12 char c; 13 for(i = 0;i < 16;i++) 14 { 15 cin >> c; 16 if(c == 'b') 17 value += (int)po[i]; 18 else continue; 19 } 20 21 for(i = 0;i < 65536;i++) 22 { 23 int cou = 0; 24 int cvalue = value; 25 for(j = 0;j < 16;j++) 26 if(i & (int)po[j]) 27 { 28 cou++; 29 cvalue ^= cs[j];//太经典了!对每一种翻法计算对应十进制加的数!然后做异或!异或就是部分取反!A与B异或就是A中B是1的位取反!!! 30 } 31 if(cvalue == 0 || cvalue == 65535) 32 if(cou < cmin) cmin = cou; 33 } 34 if(cmin == MAX) cout << "Impossible"; 35 else cout << cmin << endl; 36 return 0; 37 }