#2153. 「SCOI2005」互不侵犯(状压DP)

#2153. 「SCOI2005」互不侵犯

解题思路
令dp[i][j][k]表示第i行的状态为j时,共放置k个国王的方案数。
状态j的二进制即表示该行的放置方式,例如j为3时,放置的方式为101,即从右向左看第一位和第三位放置国王,其他位不放置。
可以预处理出每一行所有的合法状态,即两两不相邻的状态。
然后在相邻两层枚举所有合法状态,若这两层也不发生冲突,进行转移即可。
参考代码

#include <bits/stdc++.h>
using namespace std;
const int N = 10;
#define ll long long
int can[(1 << N)], tot;// 记录一行中合法的摆放方案
int cnt[(1 << N)]; // 合法方案数1的个数
ll dp[N + 1][(1 << N)][N * N + 10];
int n, k;
int sum(int x)//状态x中1的个数
{
    int t = 0;
    while (x)
    {
        if (x & 1)
            ++t;
        x >>= 1;
    }
    return t;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin >> n >> k;
    for (int i = 0; i < (1 << n); ++i)//第一层状态要手推,无法转移
    {//注意枚举范围是[0, (1 << n) - 1], 因为棋盘只有这么大
        if (!(i & (i << 1)))
        {
            can[tot] = i;
            cnt[tot] = sum(i);
            dp[1][i][cnt[tot++]] = 1;
        }
    }
    for (int i = 2; i <= n; ++i)
    {//层数
        for (int j = 0; j < tot; ++j)
        {//当前层状态
            for (int a = 0; a < tot; ++a)
            {//上一层状态
                int x = can[j], y = can[a];
                if (x & y || (x << 1) & y || (x >> 1) & y)
                    continue;//状态冲突则跳过
                for (int b = cnt[j]; b <= k; ++b)
                {//枚举国王个数
                    dp[i][x][b] += dp[i - 1][y][b - cnt[j]];
                }
            }
        }
    }
    ll ans = 0;
    for (int i = 0; i < tot; ++i)
    {
        ans += dp[n][can[i]][k];
    }
    cout << ans << endl;
    return 0;

}
posted @ 2022-12-27 17:09  何太狼  阅读(16)  评论(0编辑  收藏  举报