算法_枚举局部解空间
枚举
一、概念
策略:遍历所有解空间,逐个验证答案。
思路:对于实际问题,枚举所有可能解的代价可能很大。如果存在某个局部,一旦局部确定,其余部分唯一确定,那么只需要枚举此局部状态即可。
二、案例
案例网址:http://cxsjsxmooc.openjudge.cn/2018t2winterw1/001/
总时间限制:
1000ms
内存限制:
1024kB
描述
有一种特殊的二进制密码锁,由n个相连的按钮组成(n<30),按钮有凹/凸两种状态,用手按按钮会改变其状态。
然而让人头疼的是,当你按一个按钮时,跟它相邻的两个按钮状态也会反转。当然,如果你按的是最左或者最右边的按钮,该按钮只会影响到跟它相邻的一个按钮。
当前密码锁状态已知,需要解决的问题是,你至少需要按多少次按钮,才能将密码锁转变为所期望的目标状态。
输入
两行,给出两个由0、1组成的等长字符串,表示当前/目标密码锁状态,其中0代表凹,1代表凸。
输出
至少需要进行的按按钮操作次数,如果无法实现转变,则输出impossible。
样例输入
011
000
样例输出
1
三、案例解析
1.n<30,全部枚举空间约为536,870,912种,无法在规定时间1000ms内验证完毕。
2.事实上,除了第一个和最后一个按钮,按下中间的任何一个按钮,均会改变相邻两个按钮状态,因此如果某一位置按钮与目标密码锁状态不同,前一位置状态已经相同,则均不得按下此位置按钮,可以通过按下此位置的后一位置按钮来达到目的。
3.第一个按钮是否按下,直接决定了整个密码锁后续按钮是否需要按下。枚举空间为2,而不是229。
四、源代码
1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 void Flip(char c[], int i) 5 { 6 // 按下第i个按钮,修改状态 7 c[i] = c[i] ^ 1; 8 if(i == 0) 9 { 10 c[1] = c[1] ^ 1; 11 } 12 else if(i == 29) 13 { 14 c[28] = c[28] ^ 1; 15 } 16 else 17 { 18 c[i+1] = c[i+1] ^ 1; 19 c[i-1] = c[i-1] ^ 1; 20 } 21 } 22 int main() 23 { 24 char origin[30]; 25 char temp[30]; 26 char result[30]; 27 int switchs[30]; 28 cin >> origin >> result; 29 int len = strlen(origin); 30 for(int one =0; one <2; one++) 31 { 32 for(int i=0; i<len; i++) 33 { 34 temp[i] = origin[i]; 35 switchs[i] = 0; 36 } 37 if(one == 0) 38 { 39 //第一个按钮不需要按下 40 for(int j=0; j<len-1; j++) 41 { 42 if(temp[j] != result[j]) 43 { 44 Flip(temp, j+1); 45 switchs[j+1] = 1; 46 } 47 } 48 if(temp[len-1] == result[len-1]) 49 break; 50 } 51 if(one == 1) 52 { 53 //第一个按钮需要按下 54 Flip(temp, 0); 55 switchs[0] = 1; 56 for(int j=0; j<len-1; j++) 57 { 58 if(temp[j] != result[j]) 59 { 60 Flip(temp, j+1); 61 switchs[j+1] = 1; 62 } 63 } 64 if(temp[len-1] == result[len-1]) 65 break; 66 } 67 } 68 if(temp[len-1]==result[len-1]) 69 { 70 int n=0; 71 for(int i=0; i<len; i++) 72 { 73 n = n + switchs[i]; 74 } 75 cout << n; 76 } 77 else 78 { 79 cout << "impossible" << endl; 80 } 81 return 0; 82 }
五、更多
1.取字符c的第i位:(c >> i) & 1;
2.设置字符c的第i位为1:c |= (1 << i); 设置字符c的第i位为0:c &= ~(1 << i);
3.将c的第i位取反:c ^= (1 << i);