POJ1321 棋盘问题(DFS||状压DP)

看完这道题后,先想到八皇后问题和网络流,然后决定从八皇后问题改一改,用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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值