洛谷 P1896 [SCOI2005] 互不侵犯 题解 状压dp

文章讨论了如何使用动态规划求解在一个N×N棋盘上放置K个国王,保证彼此不互相攻击的方案数量。
摘要由CSDN通过智能技术生成

[SCOI2005] 互不侵犯

题目描述

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

输入格式

只有一行,包含两个数 N , K N,K N,K

输出格式

所得的方案数

样例 #1

样例输入 #1

3 2

样例输出 #1

16

数据范围及约定

对于全部数据, 1 ≤ N ≤ 9 1 \le N \le 9 1N9 0 ≤ K ≤ N × N 0 \le K \le N\times N 0KN×N

原题链接

洛谷P1896——传送门

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

ll n, k;              // n:棋盘边长 ,k:国王个数
vector<ll> s;         // 行合法的二进制压缩的状态(状压s)
ll dp[16][106][2006]; // dp[i][j][k],表示第i行,已选取j个国王,行状态为k
ll cnt[2006];         // 表示某个s状态有多少个1,可预处理

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> k;

    for (ll i = 0; i < (1 << n); i++)
    {                                       // 枚举二进制状态,从而选取符合条件的状态加入数组s
        if ((((i >> 1) | (i << 1)) & i) == 0) // 如果没有相邻(即 左、右 两种相邻关系)的国王,表示该状态在一行中是符合条件的
        {
            s.push_back(i);
            cnt[i] = __builtin_popcountll(i); // 编译器内置函数,计算无符号整数二进制表示中1的个数
        }
    }

    dp[0][0][0] = 1; // 第0行不放国王的状态初始化为1,用于后续dp求解

    for (ll i = 1; i <= n; i++)
    { // 枚举行
        for (ll k1 = 0; k1 < s.size(); k1++)
        { // 枚举当前行合法的行状态
            ll s1 = s[k1];
            for (ll k2 = 0; k2 < s.size(); k2++)
            { // 枚举上一行合法的行状态
                ll s2 = s[k2];
                if (((s2 | (s2 >> 1) | (s2 << 1)) & s1) == 0)
                { // 行与行之间不存在 左上、左下、右上、右下、上、下 六种相邻关系,说明此时行间状态是符合条件的
                    // 下面枚举国王数量,做好国王数量的dp
                    for (ll j = 0; j <= k; j++)
                    {                         // j表示加上当前行国王数量后 总的已选取国王数量
                        if (j - cnt[s1] >= 0) // j-cnt[s1]表示枚举到上一行时总的已选取国王数量,只有大于等于0时才是合法的(没有办法选取负数个国王doge)
                        {
                            dp[i][j][s1] += dp[i - 1][j - cnt[s1]][s2];
                        }
                    }
                }
            }
        }
    }

    ll ans = 0;
    for (ll i = 0; i < s.size(); i++)
    {
        ans += dp[n][k][s[i]]; // 统计枚举到最后一行后选取k个国王的合法状态的总方案数量
    }

    cout << ans << '\n';

    return 0;
}
  • 27
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值