Description
The game “The Pilots Brothers: following the stripy elephant” has a quest where a player needs to open a refrigerator.
There are 16 handles on the refrigerator door. Every handle can be in one of two states: open or closed. The refrigerator is open only when all handles are open. The handles are represented as a matrix 4х4. You can change the state of a handle in any location [i, j] (1 ≤ i, j ≤ 4). However, this also changes states of all handles in row i and all handles in column j.
The task is to determine the minimum number of handle switching necessary to open the refrigerator.
Input
The input contains four lines. Each of the four lines contains four characters describing the initial state of appropriate handles. A symbol “+” means that the handle is in closed state, whereas the symbol “−” means “open”. At least one of the handles is initially closed.
Output
The first line of the input contains N – the minimum number of switching. The rest N lines describe switching sequence. Each of the lines contains a row number and a column number of the matrix separated by one or more spaces. If there are several solutions, you may give any one of them.
Sample Input
-+-- ---- ---- -+--
Sample Output
6 1 1 1 3 1 4 4 1 4 3 4 4
先给出我自己的解法:迭代加深 ID-DFS搜索算法+位压缩
#include <stdio.h>
#include <string.h>
int chess;
int step, flag;
int row[20], col[20];
inline void flip(int pos) {
chess = chess^(1<<pos);
for(int i=0; i<4; ++i)
chess=chess^(1<<(pos/4*4 + i));
for(int i=0; i<4; ++i)
chess=chess^(1<<(pos%4 + i*4));
}
void dfs(int pos,int k) {
if(k>step) {
flag = (chess==0xFFFF);
return ;
}
if(flag || pos>15) return;
flip(pos);
row[k] = pos/4;
col[k] = pos%4;
if(pos==15){
flag = (chess==0xFFFF);
if (flag==1) return;
}
for(int i=pos+1;i<16;i++){
dfs(i,k+1);
if(flag==1)
break;
}
flip(pos);
}
int main() {
int i,j;
char temp[5];
chess = 0;
for(i=0; i<4; ++i) {
scanf("%s",&temp);
for(j=0; j<4; ++j)
if(temp[j]=='-')
chess = chess^(1<<(i*4+j));
}
flag = 0;
for(step=0; step<=16; step++) {
for(int j=0;j<16;j++){
dfs(j,1);
if(flag)
break;
}
if(flag)
break;
}
for(int j=0;j<16;j++){
dfs(j,1);
if(flag)
break;
}
printf("%d\n",step);
for(i=0; i<step; ++i)
printf("%d %d\n",row[i+1]+1,col[i+1]+1);
return 0;
}
迭代加深 ID-DFS搜索算法参考:
迭代加深搜索,实质上就是限定下界的深度优先搜索。即首先允许深度优先搜索K层搜索树,若没有发现可行解,再将K+1后重复以上步骤搜索,直到搜索到可行解。
迭代加深搜索算法的实现原理及基本框架
在迭代加深搜索的算法中,连续的深度优先搜索被引入,每一个深度约束逐次加1,直到搜索到目标为止。
基本框架如下:
ProcedureID-dfs(dep:integer);
Var
Begin
End;
Fori:=1todepmaxdo//枚举深度的限界
Begin
End;
迭代加深搜索算法的复杂度分析
从上述迭代加深搜索算法的实现过程和框架,我们可以看出,迭代加深搜索算法就是仿广度优先搜索的深度优先搜索。既能满足深度优先搜索的线性存储要求,又能保证发现一个最小深度的目标结点。(时间复杂度推算详见NOI导刊2010年第6期P26)
从实际应用来看,迭代加深搜索的效果比较好,并不比广度优先搜索慢很多,但是空间复杂度却与深度优先搜索相同,比广度优先搜索小很多。
迭代加深搜索算法的应用
使用搜索算法的时候,选择正确的搜索方式很重要。当有一类问题需要做广度优先搜索,但却没有足够的空间,而时间却很充裕,碰到这类问题,我们可以选择迭代加深搜索算法。
例题:POJ 2286 The Rotation Game
四、总结
一般来说,如果目标结点离根结点远,需要遍历整棵树,可以考虑使用深度优先搜索;如果目标离根结点近,或求最小步数,则考虑广度优先搜索或迭代加深搜索;若广度优先搜索存在空间不够的问题,则考虑使用迭代加深搜索。
解法一:位压缩+bfs+路径回溯(TLE)
#include <iostream>
#include <queue>
#include <stack>
#include <cmath>
#include <stdio.h>
using namespace std;
//状态转移
const int method[]={0xf888,0xf444,0xf222,0xf111,
0x8f88,0x4f44,0x2f22,0x1f11,
0x88f8,0x44f4,0x22f2,0x11f1,
0x888f,0x444f,0x222f,0x111f};
const int FIN = 0;
const int MAX = 65536;
struct Path
{
int nStep; //跳转到该状态所需步数。
int nFatherId; //父结点id
int nMethod; //当前节点是父节点的第几个孩子
};
Path stPath[MAX];
std::stack<int> Stack;
int InitData()
{
int i ;
for (i = 0 ;i < MAX ; i ++)
{
stPath[i].nFatherId = -1;
stPath[i].nMethod = -1;
stPath[i].nStep = - 1;
}
return 0;
}
int input()
{
int i ,j;
char ch;
int nInitState = 0;
for(i = 0 ;i < 4; i ++)
{
for (j = 0 ;j < 4; j ++)
{
ch = getchar();
if('+' == ch)
{
nInitState += (int)(pow(2.0,1.0*(15 - i* 4 - j)));
}
}
getchar();
}
return nInitState;
}
int bfs(int nInitState)
{
std::queue<int> Queue;
//初始状态入队列
Queue.push(nInitState);
stPath[nInitState].nStep = 0;
while(!Queue.empty())
{
int nFrontStatue = Queue.front();
Queue.pop();
int i ;
for (i = 0 ;i < 16 ;i ++)
{
int nCurStatue = nFrontStatue ^ method[i];
if (stPath[nCurStatue].nStep == -1)
{
Queue.push(nCurStatue);
stPath[nCurStatue].nStep = stPath[nFrontStatue].nStep + 1;
stPath[nCurStatue].nMethod = i;
stPath[nCurStatue].nFatherId = nFrontStatue;
}
if (nCurStatue == FIN)
{
return nCurStatue;
}
}
}
return -1;
}
int findPath(int nEnd)
{
int i;
i = nEnd;
Stack.push(stPath[i].nMethod);
while(1)
{
i = stPath[i].nFatherId;
if (stPath[i].nFatherId == -1)
{
break;
}
Stack.push(stPath[i].nMethod);
}
return 0;
}
int main()
{
int nInitState = input();
InitData();
int nRes = bfs(nInitState);
findPath(nRes);
cout<<stPath[nRes].nStep<<endl;
while (!Stack.empty())
{
int nPos = Stack.top();
Stack.pop();
cout<<nPos / 4 + 1<<" ";
cout<<nPos % 4 + 1<<endl;
}
return 0;
};
解法二:高效解法
如果要把某个+翻成-,把它所在的行和列的所有格都翻一遍就行了。那它本身翻7次,它所在行或列的单位被翻4次,其他位置的被翻2次。翻偶数次和没翻一样,翻7次和翻1次一样。那么定义一个16长的数组,对所有的+做统计,令其所在的行和列的元素都增1,最后再看一下,元素为1的位置就是被翻转过的位置,而元素为1的元素个数就是n的值。
#include <iostream>
using namespace std;
int main()
{
unsigned short n=0;
bool flag=false;
unsigned short input[16]={0};
unsigned short times[16]={0};
for(int i=0;i<16;i++)
{
char c;
std::cin >> c;
if(c=='+')
{
char row=i/4;
char column=i%4;
input[i]=1;
times[row*4]++;
times[row*4+1]++;
times[row*4+2]++;
times[row*4+3]++;
times[column]++;
times[4+column]++;
times[8+column]++;
times[12+column]++;
times[row*4+column]--;
}
else if(c!='-')
{
std::cout << "error" << std::endl;
}
}
for(int i=0;i<16;i++)
{
if(times[i]%2)
{
n++;
}
}
cout << n << endl;
for(int i=0;i<16;i++)
{
if(times[i]%2)
{
cout << i/4+1 << " " << i%4+1 << endl;
}
}
//std::cout << input << std::endl;
return 0;
}
解法三:
枚举,与解法一相似
#include <iostream>
using namespace std;
struct H{
bool ifexist;
unsigned short father;
char step;
};
unsigned short update_map(unsigned short input,char location);
int main()
{
unsigned short input=0,n=0;
bool flag=false;
for(int i=0;i<16;i++)
{
char c;
std::cin >> c;
if(c=='+')
{
input |= (1<<i);
}
else if(c!='-')
{
std::cout << "error" << std::endl;
}
}
//std::cout << input << std::endl;
unsigned short path[16][65536]={0};
struct H hist[65536];
for(int i=0;i<65536;i++)
{
hist[i].ifexist = false;
hist[i].father = 0;
hist[i].step = 0;
}
path[0][0]=input;
for(int n=1;n<=16;n++)
{
//cout << "n" << n << endl;
int k=0;
for(int j=0;path[n-1][j]!=0;j++)
{
for(char c=0;c<16;c++)
{
unsigned short tmp = update_map(path[n-1][j],c);
if(hist[tmp].ifexist)
{
continue;
}
else
{
if(tmp == 0)
{
cout << n << endl;
cout << (c/4+1) << " " << (c%4+1) << endl;
unsigned short res=path[n-1][j];
for(int k=n-1;k>0;k--)
{
char x=hist[res].step;
cout << (x/4+1) << " " << (x%4+1) << endl;
res= hist[res].father;
}
return 0;
}
hist[tmp].ifexist=true;
hist[tmp].father=path[n-1][j];
hist[tmp].step=c;
path[n][k]=tmp;
k++;
}
}
}
}
cout << "no answer" << endl;
return 0;
}
//location: where to switch the refrigerator,from 0 to 15
//i=location/4+1, j=location%4+1
unsigned short update_map(unsigned short input,char location)
{
unsigned short tmp=0;
switch(location)
{
case 0:
tmp = 0x111f;break;
case 1:
tmp = 0x222f;break;
case 2:
tmp = 0x444f;break;
case 3:
tmp = 0x888f;break;
case 4:
tmp = 0x11f1;break;
case 5:
tmp = 0x22f2;break;
case 6:
tmp = 0x44f4;break;
case 7:
tmp = 0x88f8;break;
case 8:
tmp = 0x1f11;break;
case 9:
tmp = 0x2f22;break;
case 10:
tmp = 0x4f44;break;
case 11:
tmp = 0x8f88;break;
case 12:
tmp = 0xf111;break;
case 13:
tmp = 0xf222;break;
case 14:
tmp = 0xf444;break;
case 15:
tmp = 0xf888;break;
default: break;
}
return input^tmp;
}