题目描述
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
Input
输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
Output
对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
Sample Input
2 1
#.
.#
4 4
…#
…#.
.#…
#…
-1 -1
Sample Output
2
1
方法一
普普通通的dfs
#include <iostream>
#include <cstring>
using namespace std;
int n, k;
const int N = 9;
char g[N][N];
int colum[N];
int sum;
void dfs(int row, int num)
{
if (num == k) //当个数为k时跳出递归
{
sum ++;
return;
}
if (row == n) //当超出格数时跳出递归
return;
for (int i = 0; i <= n; i ++)
if ((colum[i] == 0 && g[row][i] == '#') || (i == n))
{
if (i == n)
dfs(row + 1, num); //对应当前行不选泽
else //选择第row行第i列
{
colum[i] = 1;
dfs(row + 1, num + 1);
colum[i] = 0;
}
}
}
int main()
{
while(cin >> n >> k, n != -1 || k != -1)
{
memset(colum, 0, sizeof colum);
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++)
cin >> g[i][j];
sum = 0;
dfs(0, 0);
cout << sum << endl;
}
return 0;
}
方法二
状压DP
定义数组F[i, j]表示前i行,状态为j时,方法的总量。
j用一个二进制数表示,表示1-n行放棋子的情况,每一位表示对应的列上有没有放棋子,如果放了棋子,值就是1,否则就是0。
状态转移方程即为对第i行的j从全为0到全为1进行枚举,从F[i - 1,j]转移时,只有对应的j的二进制的某一位数是0且当前棋盘是“#”,才表示可以转移。
时间复杂度O(n² 2^n)
#include <iostream>
#include <cstring>
const int N = 9;
using namespace std;
char g[N][N];
int f[N][1 << N];
int n, m;
int cnt(int x)
{
int res = 0;
while (x)
{
if (x & 1)
res ++;
x = x >> 1;
}
return res;
}
int main()
{
while (cin >> n >> m, n != -1 || m != -1)
{
int sum = 0;
memset(f, 0, sizeof f);
f[0][0] = 1;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
cin >> g[i][j];
for (int i = 1; i <= n; i ++)
for (int k = 0; k < 1 << n; k ++)
{
f[i][k] = f[i - 1][k];
for (int j = 1; j <= n; j ++)
{
if (g[i][j] == '#' && k >> (j - 1) & 1)
f[i][k] += f[i - 1][k - (1 << (j - 1))];
}
}
for (int i = 1 << (m - 1); i < 1 << n; i ++)
if (cnt(i) == m)
{
sum += f[n][i];
}
cout << sum << endl;
}
return 0;
}