001:特殊密码锁
总时间限制:
1000ms
内存限制:
1024kB
描述
有一种特殊的二进制密码锁,由n个相连的按钮组成(n<30),按钮有凹/凸两种状态,用手按按钮会改变其状态。
然而让人头疼的是,当你按一个按钮时,跟它相邻的两个按钮状态也会反转。当然,如果你按的是最左或者最右边的按钮,该按钮只会影响到跟它相邻的一个按钮。
当前密码锁状态已知,需要解决的问题是,你至少需要按多少次按钮,才能将密码锁转变为所期望的目标状态。
输入
两行,给出两个由0、1组成的等长字符串,表示当前/目标密码锁状态,其中0代表凹,1代表凸。
输出
至少需要进行的按按钮操作次数,如果无法实现转变,则输出impossible。
分析:
枚举法,且第一位应分按下和不按下两种情况,因为这将影响后面的布局,比如数据1110,1111,若是单单判断到末尾才考虑将按下和不按下将为时已晚。
在网上看了两篇文章
第一篇:第1篇
他的代码可以通过测试。
这篇相当详尽,但有一些不足之处,对超过边界的未给予一个很好的约束,比如sw里面就有一些漏洞,仅仅用size来弄,在我看来是不能满足题意的。
第二篇:第2篇
他的代码也可以通过测试。
但有一些漏洞,就如评论所说1111,0000跑出1??,明显这是不对的,只能说openjudge的测试数据不完善,出现了漏网之鱼,但他的大意还是值得借鉴的,但相较于第一篇来说,他的漏洞实在太大。
我将第一篇的代码优化和简化了一下,他的代码提供了记录按下按钮的位置,我们可以将之去掉达到简化目的,除此之外,添加了很多注释供理解。
#include <iostream>
#include <cstring>
using namespace std;
const int MAX = 100;
void sw(char a[], int i, int s){
if(i==0&&s>1) //i为首位,i+1位取反
a[i+1] ^= 1;
else if(i==s-1) //i为末位,i-1位取反
a[i-1] ^= 1;
else{ //i为中间位, i-1,i+1取反
a[i+1] ^= 1;
a[i-1] ^= 1;
}
a[i] ^= 1;
}
int main(){
char ori[MAX];//原状态
char v[MAX];//变化中的状态
char result[MAX];//目标状态
int num=0;//按下数
cin >> ori >> result;
int n = strlen(ori);
for(int k=0; k<2; ++k){ //k有两个值,代表首位按不按下两种情况
num=0;
strcpy(v, ori); //将原复制到变化中
if(k==0){//当第一个开关为关闭状态时,也即不按下首位
for(int i=0; i<n-1; ++i){
if(v[i]!=result[i]){ //变化与目标不符
sw(v, i+1, n); //改变了变化状态
num++;
}
}
if(result[n-1]==v[n-1]) break; //如果目标的最后一位和变化的最后一位相等,则退出for循环
}
else{//第一个开关为打开时, 也即按下首位
sw(v, 0,n);
num++;
for(int i=0; i<n-1; ++i){
if(v[i]!=result[i]){
sw(v, i+1, n);
num++;
}
}
}
}
if(v[n-1]==result[n-1]){
cout << num;
}
else
cout << "impossible" << endl;
return 0;
}
在这里,有一些有意思的点,比如:
-
第2篇要去比较操作数的大小,这里明明没最小啊?
实际上,k=0,不按首位肯定会比k=1按下首位少操作数,所以比较大小就没必要了,只要知道末尾相等不相等就行了,这里也正是第2篇错误的原因,原因分析: 1111,0000进入时,它首先按下2号位,当运行到4号位时,虽然不相等,但前一位相等,所以Change不了,这时num1=1;
到了按下首位,得到num2=2,虽然是正确的,但由于取最小,导致min_num=1,所以错误。 -
还有一点就是为什么只需要判断最后一位?
这是因为在前面的判断中就已经判断过了,不同我们才按下的,所以最后一位将是成不成不成功的标志。 -
枚举的精髓在于:找到局部,缩小枚举范围,省脑袋。