原题:
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
样例输入:
2 1 #. .# 4 4 ...# ..#. .#.. #... -1 -1输出:
2
1
这题我第一感觉是用回溯法,于是很快写好了代码,但发现有些问题:程序并没有很好的完成回溯的任务。最后加上了一个关键语句简单的实现了回溯:
include <iostream>
using namespace std;
int k,cnt,ptr,pp;
struct point
{
int x;
int y;
}s[100];
point pla[10];
void place(point *a,int cur)
{
pla[pp].x=a->x;
pla[pp].y=a->y;
pp++;
if(cur==k)
{
cnt++;
}
else for(int i=1;;i++)
{
if((a+i-1)->x==s[ptr-1].x&&(a+i-1)->y==s[ptr-1].y){break;}
int ok=1;
for(int j=0;j<pp;j++)
{
if(pla[j].x==(a+i)->x||pla[j].y==(a+i)->y)
{
ok=0;
}
}
if(ok){
place(a+i,cur+1);
pp--;//事实上,回溯的关键就是在递归调用之后加上一个回溯标志性语句。
}
}
}
int main()
{
int n;
while(cin>>n>>k&&n!=-1&&k!=-1)
{
ptr=0;
cnt=0;
char map[n][n];
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
cin>>map[i][j];
if(map[i][j]=='#')
{
s[ptr].x=i;
s[ptr].y=j;
ptr++;
}
}
}
for(int i=0;i<ptr;i++)
{
pp=0;
place(s+i,1);
}
cout<<cnt<<endl;
}
return 0;
}
但ac后发现时间240ms,发现自己干了很蠢的事:同一行的棋盘位我也搜索了!
这里使用的搜索本质上是DFS,下面转一个别人写好的按行DFS的代码:
/Memory Time
//184K 32MS
#include<iostream>
using namespace std;
bool chess[9][9];
bool vist_col[9]; //列标记
int status; //状态计数器
int n,k;
void DFS(int row,int num) //逐行搜索,row为当前搜索行,num为已填充的棋子数
{
if(num==k)
{
status++;
return;
}
if(row>n) //配合下面DFS(row+1,num); 语句使用,避免搜索越界
return;
for(int j=1;j<=n;j++)
if(chess[row][j] && !vist_col[j])
{
vist_col[j]=true; //放置棋子的列标记
DFS(row+1,num+1);
vist_col[j]=false; //回溯后,说明摆好棋子的状态已记录,当前的列标记还原
}
DFS(row+1,num); //这里是难点,当k<n时,row在等于n之前就可能已经把全部棋子放好
//又由于当全部棋子都放好后的某个棋盘状态已经在前面循环时记录了
//因此为了处理多余行,令当前位置先不放棋子,搜索在下一行放棋子的情况
return;
}
int main(int i,int j)
{
while(cin>>n>>k)
{
if(n==-1 && k==-1)
break;
/*Initial*/
memset(chess,false,sizeof(chess));
memset(vist_col,false,sizeof(vist_col));
status=0;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
char temp;
cin>>temp;
if(temp=='#')
chess[i][j]=true;
}
DFS(1,0); //注意初始化的值别弄错了
cout<<status<<endl;
}
return 0;
}
/(代码来自小優YoU)
需要注意的是,与八皇后不同,棋子可能比行少。如果不像我那样不动脑子的暴力,就必须加上DFS(row+1,num)来跳过行。
最后再强调一下,dfs后把vis设为零是回溯的标志。