[BZOJ 1087][SCOI 2005] 互不侵犯King 状态压缩DP

题目传送门:【BZOJ 1087】

题目大意:你需要在N×N的棋盘里面放K个国王使他们互不攻击。国王能攻击到它上下左右,以及左上、左下、右上、右下共八个方向上附近的各一个格子,共8个格子。求最终有多少种摆放方案。
( 1 ≤ n ≤ 9,0 ≤ k ≤ n 2 )

题目分析:
一开始以为是道大水题,于是直接写了个 dfs 跑暴力,然后……TLE
……
其实这道题观察到后面就会发现,n = 9 时,k = 9 已经有 60亿种情况,根本没法用搜索做,唯一的方式就是 DP。
考虑到棋盘上每一个格子只能为放或不放,每一行可能的状态(不考虑合法性)最多有 29=512 种,并且由当前行的状态可以推出下一行有哪些状态是合法的(可能会有很多种),因此我们可以使用状压 DP。
首先考虑在一行以内合法的状态,只有当国王之间互不相邻时才是合法的。接下来考虑两行之间。由于以国王为中心所在的九宫格内只能有一个棋子,即国王本身,所以在判断两行之间时,除了判断这个国王下面是否有棋子外,还需要判断它的两个斜侧有无棋子。最终将合法状态转移至 DP 方程中即可。

DP 的几个维度:dp[n][state][k] 分别代表第 n 行,状态为 state,使用了 k 个国王。

下面附上代码:


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MX = 520;

int state[MX],num[MX];                    //state:当前行的合法状态 
long long dp[10][MX][MX];                 //num:当前状态所需的国王数量 
bool cond[MX][MX];                        //cond:两行之间的合法状态 
int maxs,tots = 0,n,K;                    //maxs:状态总数的最大值 
long long ans = 0;                        //tots:合法的状态总数 

void check(){                             //判断两行之间的状态是否合法 
    for (int i = 1;i <= tots;i++){
        int o = state[i];
        for (int j = 1;j <= tots;j++){
            int q = state[j];
            if (q & o) continue;          //分别判断是否为 010 或 010 或 010 
            if ((q << 1) & o) continue;   //               010    100    001
            if ((q >> 1) & o) continue;   //(1为放棋子,0为不放棋子)
            cond[o][q] = cond[q][o] = true;
        }
    }
}
void solve(){
    dp[0][0][0] = 1;
    for (int i = 1;i <= n;i++){
        for (int j = 1;j <= tots;j++){
            int o = state[j];
            for (int k = 1;k <= tots;k++){
                int q = state[k];
                if (cond[o][q]){
                    for (int p = num[j]+num[k];p <= K;p++){
                        dp[i][q][p] += dp[i-1][o][p-num[k]]; //由上一个状态转移而来 
                    }
                }
            }
        }
    }
}
int main(){
    cin>>n>>K;
    maxs = (1 << n) - 1;
    for (int i = 0;i <= maxs;i++){
        int t = i,cnt = 0;
        bool ok = 1;
        while (ok && t){
            if ((t & 3) == 3){            //判断是否为 11 类型 
                ok = 0;                   //即:两个国王是否相邻 
            }
            if (t & 1) cnt++;
            t >>= 1;
        }
        if (ok){
            state[++tots] = i;
            num[tots] = cnt;
        }
    }
    check();
    solve();
    for (int j = 1;j <= tots;j++)
        ans += dp[n][state[j]][K];        //按照最后要求的状态输出 
    cout<<ans<<endl;
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值