8469:特殊密码锁
问题描述
有一种特殊的二进制密码锁,由n个相连的按钮组成①(n<30),按钮有凹/凸两种状态,用手按按钮会改变其状态。
然而让人头疼的是,②当你按一个按钮时,跟它相邻的两个按钮状态也会反转。当然,如果你按的是最左或者最右边的按钮,该按钮只会影响到跟它相邻的一个按钮。
当前密码锁状态已知,需要解决的问题是,你③至少需要按多少次按钮,才能将密码锁转变为所期望的目标状态。
输入
两行,给出两个由0、1组成的等长字符串,表示当前/目标密码锁状态,其中0代表凹,1代表凸。
输出
至少需要进行的按按钮操作次数,如果无法实现转变,则输出impossible。
样例输入
011 000
样例输出
1
解题思路
1、阅题可发现本题的密码均是由一组01串组成,则可首先考虑是否能运用位运算进行求解
2、01串的组成最多不超过29位,根据数据类型判断,可以选用一个int类型(32位)的变量存储该01串。
3、当确定使用int类型变量存储密码串后会发现C++中并不能直接用二进制数对变量进行直接赋值,这时应思
考如何对01串进行保存?字符数组。
4、无疑,字符数组确实可以保存01字符串,但若是想直接通过字符数组操作实现从当前状态到目标状态是困
难的,费时的,耗内存的。我们更愿意去使用最先想到的int类型的变量去存储01的字符串,所以此时我们应考虑如
何实现用int类型的变量去存储一个01串?结合字符数组,设计一个函数使char类型的数组转换为一个int类型的变量
int origin = 0; //当前密码锁状态
char Origin[32] = {0}; //定义一个char类型的数组作为临时存储
int target = 0; //目标密码锁状态
char Target[32] = {0}; //定义一个char类型的数组作为临时存储
void setBit(int i, int j)
{
origin |= (int(Origin[j])-48 << i);
target |= (int(Target[j])-48 << i);
}
cin >> Origin >> Target;
int len = strlen(Origin)-1; //确定密码锁的位数
for(int i = len, j = 0; i >= 0; i--, j++) //将字符数组里面的数据录入origin和target中
{
setBit(i, j);
}
为方便使用各个变量,前四行定义的变量均为全局变量
5、当实现用int类型存储01串后,按由②可直接推出密码锁的按钮连续按两次与没有按是同一种效果,因此密码锁的按钮只有按与不按两种方式。若是将各个可能逐个试出再比较最小按下次数这必然需要一个庞大的循环次数(2^n)。我们思考如何减少循环的次数?若是存在某个局部,一旦该局部的状态被确定下来剩下的其他部分也会相应的被确定下来。如此,我们可以发现,每一个按钮都可以是一个局部,若有一个按钮先被确定下来,剩下的按钮也会被确定,这样我们就可以原来应操作的2^n次计算降低为n次计算,大大减少了计算量。
void switchs(int i, int len)
{
nums++;
replace ^= (1 << i);
if(i == len)
{
replace ^= (1 << (i-1));
}
else if(i == 0)
{
replace ^= (1 << (i+1));
}
else
{
replace ^= (1 << (i-1));
replace ^= (1 << (i+1));
}
}
int len = strlen(Origin)-1; //确定密码锁的位数
for(int i = len; i >= 0; i--)
{
switchs(i, len); //循环各个局部
}
6、当我们按下n个按钮中一个i时,其他当前状态下的按钮根据i左右扩散靠近的一个确定是否应该按下
例:origin:00010010
target:00100100
当我们选择按下第5个按钮(i)时,origin会变为00001110
接着以i左右扩散对比target进行判断是否需要按下剩下的按钮
例题可看出origin的第4个按钮与target的第4个按钮相同,因此第3个按钮不需要按下。
而origin的第3个按钮与target的第3个按钮不同,因此我们需要按下origin的第2个按钮
以此依次进行判断。
注:当我们按下第i个按钮时可能第i个会与target出现不同状态,此时我们便需要进行两次判断,第一
次按下i+1的按钮后与上面操作方式相同得出结果,第二次按下i-1的按钮后与上面进行相同操作得
出结果,两个结果进行对比后返回小值。(为反正数据被破坏,应用一个变量replace代替origin
进行操作)
int change(int i, int len)
{
int temp = target & (1 << i); //定义两个变量判断i按钮是否相同
int Temp = replace & (1 << i);
if(temp == Temp)
{
for(int left = i+2; left <= len; left++)
{
temp = target & (1 << (left-1));
Temp = replace & (1 << (left-1));
if(temp != Temp)
{
nums++;
replace ^= (1 << left);
if(left == len)
{
replace ^= (1 << (left-1));
}
else
{
replace ^= (1 << (left+1));
replace ^= (1 << (left-1));
}
}
}
for(int right = i-2; right >=0; right--)
{
temp = target & (1 << (right+1));
Temp = replace & (1 << (right+1));
if(temp != Temp)
{
nums++;
replace ^= (1 << right);
if(right == 0)
{
replace ^= (1 << right+1);
}
else
{
replace ^= (1 << right+1);
replace ^= (1 << right-1);
}
}
}
if(replace == target) //若最终origin表现状态与target状态相同则返回操作次数,不同则返回0;
{
return nums;
}
else
{
return 0;
}
}
else
{
int Lnums = nums; //当replace中第i个按钮与target的状态不同时,会有两种情况,
int Lreplace = replace; //此时使用一些新的变量存储计算,防止原有数据被破坏掉而无法进行第二种情况
for(int left = i+1; left <= len; left++)
{
temp = target & (1 << (left-1));
Temp = Lreplace & (1 << (left-1));
if(temp != Temp)
{
Lnums++;
Lreplace ^= (1 << left);
if(left == len)
{
Lreplace ^= (1 << (left-1));
}
else
{
Lreplace ^= (1 << (left+1));
Lreplace ^= (1 << (left-1));
}
}
}
for(int right = i-2; right >=0; right--)
{
temp = target & (1 << right+1);
Temp = Lreplace & (1 << right+1);
if(temp != Temp)
{
Lnums++;
Lreplace ^= (1 << right);
if(right == 0)
{
Lreplace ^= (1 << right+1);
}
else
{
Lreplace ^= (1 << right+1);
Lreplace ^= (1 << right-1);
}
}
}
int Rreplace = replace;
int Rnums = nums;
for(int left = i+2; left <= len; left++)
{
temp = target & (1 << left-1);
Temp = Rreplace & (1 << left-1);
if(temp != Temp)
{
Rnums++;
Rreplace ^= (1 << left);
if(left == len)
{
Rreplace ^= (1 << left-1);
}
else
{
Rreplace ^= (1 << left+1);
Rreplace ^= (1 << left-1);
}
}
}
for(int right = i-1; right >=0; right--)
{
temp = target & (1 << right+1);
Temp = Rreplace & (1 << right+1);
if(temp != Temp)
{
Rnums++;
Rreplace ^= (1 << right);
if(right == 0)
{
Rreplace ^= (1 << right+1);
}
else
{
Rreplace ^= (1 << right+1);
Rreplace ^= (1 << right-1);
}
}
}
if(Lreplace == target) //判断哪种情况下origin会有target状态相同,若均相同返回需要次数少的
{ //其中一种情况相同返回此情况的次数,均不同则返回0
if(Rreplace == target)
{
if(Lnums > Rnums)
{
return Rnums;
}
else
{
return Lnums;
}
}
else
{
return Lnums;
}
}
else if(Rreplace == target)
{
if(Lreplace == target)
{
if(Lnums > Rnums)
{
return Rnums;
}
else
{
return Lnums;
}
}
else
{
return Rnums;
}
}
else
{
return 0;
}
}
}
7、在操作完每种局部的结果后应用一组int类型的数组存储change()函数所返回的数据,以便最后遍历取出最少下按钮得出结果的次数。
下面为源代码:
#include
#include
#include
using namespace std;
int origin = 0; //当前密码锁状态
char Origin[32] = {0}; //定义一个char类型的数组作为临时存储
int replace = 0; //临时存储origin的值
int target = 0; //目标密码锁状态
char Target[32] = {0}; //定义一个char类型的数组作为临时存储
int nums = 0; //计算需要按按钮的次数
void setBit(int i, int j)
{
origin |= (int(Origin[j])-48 << i);
target |= (int(Target[j])-48 << i);
}
void switchs(int i, int len)
{
nums++;
replace ^= (1 << i);
if(i == len)
{
replace ^= (1 << (i-1));
}
else if(i == 0)
{
replace ^= (1 << (i+1));
}
else
{
replace ^= (1 << (i-1));
replace ^= (1 << (i+1));
}
}
int change(int i, int len)
{
int temp = target & (1 << i);
int Temp = replace & (1 << i);
if(temp == Temp)
{
for(int left = i+2; left <= len; left++)
{
temp = target & (1 << (left-1));
Temp = replace & (1 << (left-1));
if(temp != Temp)
{
nums++;
replace ^= (1 << left);
if(left == len)
{
replace ^= (1 << (left-1));
}
else
{
replace ^= (1 << (left+1));
replace ^= (1 << (left-1));
}
}
}
for(int right = i-2; right >=0; right--)
{
temp = target & (1 << (right+1));
Temp = replace & (1 << (right+1));
if(temp != Temp)
{
nums++;
replace ^= (1 << right);
if(right == 0)
{
replace ^= (1 << right+1);
}
else
{
replace ^= (1 << right+1);
replace ^= (1 << right-1);
}
}
}
if(replace == target)
{
return nums;
}
else
{
return 0;
}
}
else
{
int Lnums = nums;
int Lreplace = replace;
for(int left = i+1; left <= len; left++)
{
temp = target & (1 << (left-1));
Temp = Lreplace & (1 << (left-1));
if(temp != Temp)
{
Lnums++;
Lreplace ^= (1 << left);
if(left == len)
{
Lreplace ^= (1 << (left-1));
}
else
{
Lreplace ^= (1 << (left+1));
Lreplace ^= (1 << (left-1));
}
}
}
for(int right = i-2; right >=0; right--)
{
temp = target & (1 << right+1);
Temp = Lreplace & (1 << right+1);
if(temp != Temp)
{
Lnums++;
Lreplace ^= (1 << right);
if(right == 0)
{
Lreplace ^= (1 << right+1);
}
else
{
Lreplace ^= (1 << right+1);
Lreplace ^= (1 << right-1);
}
}
}
int Rreplace = replace;
int Rnums = nums;
for(int left = i+2; left <= len; left++)
{
temp = target & (1 << left-1);
Temp = Rreplace & (1 << left-1);
if(temp != Temp)
{
Rnums++;
Rreplace ^= (1 << left);
if(left == len)
{
Rreplace ^= (1 << left-1);
}
else
{
Rreplace ^= (1 << left+1);
Rreplace ^= (1 << left-1);
}
}
}
for(int right = i-1; right >=0; right--)
{
temp = target & (1 << right+1);
Temp = Rreplace & (1 << right+1);
if(temp != Temp)
{
Rnums++;
Rreplace ^= (1 << right);
if(right == 0)
{
Rreplace ^= (1 << right+1);
}
else
{
Rreplace ^= (1 << right+1);
Rreplace ^= (1 << right-1);
}
}
}
if(Lreplace == target)
{
if(Rreplace == target)
{
if(Lnums > Rnums)
{
return Rnums;
}
else
{
return Lnums;
}
}
else
{
return Lnums;
}
}
else if(Rreplace == target)
{
if(Lreplace == target)
{
if(Lnums > Rnums)
{
return Rnums;
}
else
{
return Lnums;
}
}
else
{
return Rnums;
}
}
else
{
return 0;
}
}
}
int main()
{
int result[100] = {0}; //存储各类方式解锁需要按按钮的次数
cin >> Origin >> Target;
int len = strlen(Origin)-1; //确定密码锁的位数
for(int i = len, j = 0; i >= 0; i--, j++) //将字符数组里面的数据录入origin和target中
{
setBit(i, j);
}
if(origin == target) //进行第一次比较若一开始当前状态便与目标状态一致则输出0并结束程序
{
cout << nums;
return 0;
}
for(int i = len; i >= 0; i--)
{
nums = 0; //每次循环重置一次按按钮需要次数
replace = origin; //每次循环用临时数据存储当前状态进行计算
switchs(i, len);
int final = change(i, len);
result[i] = final;
}
int min = 1000;
for(int i = 0; i <= len; i++)
{
if((min > result[i]) && (result[i] != 0))
{
min = result[i];
}
}
if(min != 1000)
{
cout << min;
}
else
{
cout << "impossible";
}
return 0;
}