列舉-OpenJudge-8469-特殊密碼鎖
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<iostream>
#include<cstdio>
#include<cstring>
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;
}