看完这道题后,先想到八皇后问题和网络流,然后决定从八皇后问题改一改,用DFS做。
每一行有两种可能,一种是放一个棋子,然后最该列进行标记;另一种是不放棋子,直接搜索下一行。但是我第一次敲却是因为代码能力太弱,敲着敲着就敲到八皇后上去了,没有考虑这一行不放棋子的情况,后来还是看到别人的样例才醒悟,这要是在现场赛上,就算过了也要WA很多次吧……
做完之后,又想到了状态压缩DP的解法,虽然在这道题的数据下大材小用了,但这不是重点。DP也很好想,一共2n种状态,每种状态记录可以到达这种状态的路径数,之后一行一行的递归即可。敲DP的时候,我代码能力弱又体现出来了——DP[0][0]是一定可以到达的,所以要初始化为1,之后每一行的DP[i][0]状态也一定是可以到达的,所以要用DP[i][0]+=DP[i-1][0]
DFS时间复杂度O(nn),状压DP为O(n22n),运行时间如下,第一行是状压DP,0MS,第二行是DFS,47MS:
DFS的代码如下:
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;
const int MAXN = 10;
int n,k,ans;
char g[MAXN][MAXN];
bool col_flag[MAXN];
int done;
void dfs(int row)
{
int i;
if (row == n)
return;
for (i = 0; i < n; ++i)
{
if (col_flag[i] || g[row][i] == '.')
continue;
++done;
if (done == k)
++ans;
col_flag[i] = true;
dfs(row+1);
col_flag[i] = false;
--done;
}
dfs(row+1); //注意和八皇后问题的区别,某一行中可能有棋子,也可能没有棋子。
}
int main()
{
int i,j;
while (scanf("%d %d",&n,&k) && n>0)
{
ans = 0;
for (i = 0; i < n; ++i)
for (j = 0; j < n; ++j)
cin>>g[i][j];
done = 0;
memset(col_flag,0,sizeof(col_flag));
dfs(0);
printf("%d\n",ans);
}
return 0;
}
状压DP代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 10;
char g[MAXN][MAXN];
int n,k;
int dp[MAXN][1<<MAXN];
int cal(int x)
{
int i,res = 0;
while (x)
{
if (x&1)
++res;
x>>=1;
}
return res;
}
int main()
{
int i,j,p,q;
int num[1<<MAXN];
for (i = 0; i < 1<<MAXN; ++i)
num[i] = cal(i);
while (scanf("%d %d",&n,&k) && n > 0)
{
memset(dp,0,sizeof(dp));
for (i = 0; i < n; ++i)
for (j = 0; j < n; ++j)
cin>>g[i][j];
int S = 1 << n;
dp[0][0] = 1; //这里一定要注意,在第0行不放棋子的话就可以到达dp[0][0]的状态,所以dp[0][0]=1,否则会漏很多解
for (p = 0; p < n; ++p)
if (g[0][p] == '#')
dp[0][1<<p] = 1;
for (i = 1; i < n; ++i)
{
for (j = 0; j < S; ++j)
{
if (num[j] > k) //一个剪枝,如果这个状态里已经放了多余k个棋子,可以直接跳过
continue;
dp[i][j] += dp[i-1][j];
for (p = 0; p < n; ++p)
{
if (g[i][p] == '#' && (j&(1<<p))==0)
{
q = j|(1<<p);
dp[i][q] += dp[i-1][j];
}
}
}
}
int ans = 0;
for (i = 0; i < S; ++i)
if (num[i] == k)
ans += dp[n-1][i];
printf("%d\n",ans);
}
return 0;
}