SGU 223(状压+dp)

// 题意:在一个n * n的棋盘上,放k个棋子使得每个棋子的周围八个区域都不得有其他棋子,问这样放置共有多少种
// 方法:状压+dp的水题 这几天都要被状压弄疯了
// dp[i][j][k] 代表第i行状态为j棋盘上总共放了k个棋子的方案数
// 然后由此行的状态j推出下一行不冲突的状态l就好了
// 巧妙利用 位运算和与运算来判断冲突 又快又方便
#include "iostream"
#include "string.h"
using namespace std;

int n, m;
long long dp[11][1<<10][105];
long long cnt[(1<<10)];
int calcu(int x)
{
    int ans = 0;
    for(int i = 0; i < 10; i++)
        if(x & (1<<i)) ans++;
    return ans;
}

int main()
{
    for(int i = 0; i < (1<<10); i ++)
        cnt[i] = calcu(i);
    while(cin>>n>>m)
    {
        if(n%2 == 0 && m > (n * n / 4)) { //自作主张 稍微剪枝下 如果n是偶数 m不得超过(n*n)/2
            cout<<0<<endl;
            continue;
        }
        int maxn = (1<<n) - 1;
        memset(dp, 0, sizeof dp);
        for(int i = 0; i <= maxn; i++)
        {
            if(i & (i<<1)) continue;
            if(cnt[i] <= m)
                dp[1][i][cnt[i]] = 1;
        }
        for(int i = 1; i <= (n - 1); i++)          //处理到第I行
        {
            for(int k = 0; k <= m; k++)            //已经放了k个棋子
                for(int j = 0; j <= maxn; j ++)    //当前行的状态为j
                {
                    if(j & (j<<1)) continue;       //如果本行的状态不合法直接跳过
                    for(int l = 0; l <= maxn; l++) //枚举下一行的状态l
                    {
                        if(l & (l<<1)) continue;   //如果状态l不合法直接跳过
                        if((l & j) || (l & (j<<1)) || (l & (j>>1))) continue;        //如果两状态冲突
                        int t2 = cnt[l];           //计算出下一行放置棋子的数量
                        if(k + t2 < m)             //如果当前所有放置棋子总和小于m
                            dp[i + 1][l][k + t2] += dp[i][j][k];
                        else if(k + t2 == m)
                            dp[i + 1][l][m] += dp[i][j][k];
                    }
                }
        }
        long long ans = 0;
        for(int j = 0; j <= maxn; j++) //把最后一行所有可行状态且总共放置m个棋子的所有方案数相加输出即可
            ans += dp[n][j][m];
        cout<<ans<<endl;
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章