Loading

SCOI2005 互不侵犯(状压DP)

SCOI2005 互不侵犯(状压DP)

题目大意

\(N×N\)的棋盘里面放\(K\)个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

Input

一行\(N,K\)

Output

方案数

Solution

状态压缩DP基础
状压和二进制有着不可割舍的联系
一帮情况下,(我)习惯把状态抽象成一维
并且用二进制表示,然后再去想怎么压缩状态

比如互不侵犯这个题
根据题目中给出的变量
直接定义dp[i][j][k]表示
第i行在状态为j时,共用了k个国王的方案数
那么如何判断上下左右有没有重复呢
这就用到二进制的运算&
然后预处理所有不必转移的状态和所有的状态
所有状态是指n位数的所有状态

例如n=3
所有状态就是
000,001,010,011,100,101,110,111

num数组记录当前状态的国王数量
第一能够减小常数
第二作为状态转移的初始状态

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;

inline int read(){
    int x = 0, w = 1;
    char ch = getchar();
    for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
    for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
    return x * w;  
}

const int maxn = 520;
int dp[20][maxn][maxn];
int s[maxn], num[maxn];
int cnt, n, k;

inline void pre(){
    cnt = 0;
    for(int i = 0; i < (1 << n); i++){
        if(i & (i << 1)) continue;
        int sum = 0;
        for(int j = 0; j < n; j++)
            if(i & (1 << j)) ++sum;
        s[++cnt] = i;   
        num[cnt] = sum;
    }
}

signed main(){
    n = read(), k = read();
    pre();
    dp[0][1][0] = 1;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= cnt; j++)
            for(int l = 0; l <= k; l++)
            if(l >= num[j]){
                for(int t = 1; t <= cnt; t++){
                    if(!(s[t] & s[j]) && !(s[t] & s[j] << 1) && !(s[t] & s[j] >> 1))
                        dp[i][j][l] += dp[i - 1][t][l - num[j]];
                }
            }
    int ans = 0;
    for(int i = 1; i <= cnt; i++)
        ans += dp[n][i][k];
    cout << ans << '\n';
    return 0;
}
posted @ 2020-04-12 16:51  Gary_818  阅读(95)  评论(0编辑  收藏  举报