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;
}

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