SCOI2005 互不侵犯 - 状压DP

本文深入探讨状态压缩动态规划(状压DP)在解决多维限制问题中的应用,通过具体实例讲解如何划分阶段、确定决策及转移。分享了如何利用位运算优化状态检查,减少内存消耗,并给出了一行国王放置问题的代码实现,最后总结了解题经验和技巧。

考虑如何划分阶段,如何确定决策及转移
阶段就是子问题的规模,对于这道题,可以说“更小的棋盘,更少的king”算是子问题,但是我们难以表示“更小的棋盘”,难道要表示出行列吗?
我们可以灵活变换状态,更小的棋盘,并不代表我非要按相似比缩小棋盘,如果仅仅是“更小”,只要满足更小的要求,完全可以设计一个小的更加特殊的状态:按行来划分阶段,棋盘宽不变,小的仅仅是行,这样也满足“更小”的约定。虽然我没法改变需要更小的规模这个事实,但我可以决定怎么样小的方法
而决策也随着状态发生改变。本来我想一个个下king,但是在这种状态表示下无法表示一个个放king,干脆就一行行放king,而放king要受到上一行的制约,因此设f[i][j][k]为前行放了j个king,第i行放king的状态为k,并预处理出一行中互不侵犯的king的二进制状态
另外 检查上下两行是否符合题意时,不必一位一位单独拿出来看看是否冲突,可以直接对在下方的整体二进制状态左移右移,然后判断上下行按位与是否为true

状压DP,适用于解决有多维限制的问题,比如说一个点能不能放在一个位置,取决于和他同行同列或者怎么怎么样,反正不止一重限制,但是同时处理多维限制的方法貌似最好想的就是暴力检验。
先按其中一种限制划分阶段,比如说按行,而另一种限制提前处理好,用暴力的方式处理出一行里满足限制怎么怎么放的情况,然后把行和行按列的限制“拼起来”,就可以转移了。并不是一个个地转移,而是一行行地,并且提前考虑好行内情况。
比较体现这个思想的题:AHOI2009 中国象棋(请注意!!!下一行剧透解法,想体验完整做题感勿看)













中国象棋这道题,提前考虑了行内情况,一行只能放两个炮,那么我就两个炮一起下!(就像这道题一行国王一起放一样)
另外,这题给我的启示:
1.判断一个二进制是否合题意,尽量用位运算,别一个个左移1,然后比一下那种的…比如说要求的二进制不能有两个相邻的 1,那么我们只要把这个二进制左移一下,再和原来的比较就可以了
2.为了减小常数和减少数组中无用的内存,用一个编号代替这个二进制,这就是为什么要预处理
3.方程中明确好二进制和二进制编号的区别,尽量都用一种表示法。。不然出了错很难看出来
4.如果说边界情况不好处理,可以预先处理出第一行
5.方案数如果不让模很有可能是long long 输出一定记得要写%lld
6.如果方程设的是f(i,j,k) 那么 枚举顺序最好也要这样枚举…当然也要看看是不是要这样枚举
7.二进制枚举到(1<

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int MAXN = 1000 + 10;
typedef long long ll;
int n,k,cou[1<<10],g[1<<10],tot,temte[MAXN];
ll ans,f[10][1<<10][100];
void print(int k) {//???,?????
     int temtot = 0;
     for(int i=0; i<n; i++) 
        temte[++temtot] = (k >> i) & 1;
     for(int i=temtot; i>=1; i--) 
        cout <<temte[i];
     cout << endl;
}
void init() {
    for(int i=0; i<(1<<n); i++) {
        if(i&(i>>1)) continue;
        int sum = 0;
        int t = i;
        while(t) {
            sum += t&1;
            t>>=1;
        }
        g[++tot] = i;
        cou[tot] = sum;
    }
}
bool check(int x, int y) {
    if(!(x&y) && !((x<<1) & y) && !((x>>1) & y )) {
        return true;
    }
    return false;
}
int main() {
    cin >> n >> k;
    init();
    for(int i=1; i<=tot; i++) {
        f[1][i][cou[i]] = 1;
    }
    for(int i=2; i<=n; i++) {
            for(int j=1; j<=tot; j++) {
                for(int z=1; z<=tot; z++) {
                    if((g[z]&g[j]) || (g[z]&(g[j]>>1)) || (g[z]&(g[j]<<1))) continue;
                    for(int kk=cou[j]; kk<=k; kk++)
                            f[i][j][kk] += f[i-1][z][kk-cou[j]];

                }
            }

    }
    for(int i=1; i<=tot; i++) {
        ans += f[n][i][k];
    }
    printf("%lld\n", ans);
    return 0;
}
### 关于SCOI2005 互不侵犯问题的DFS解法 对于SCOI2005 互不侵犯这一问题,采用深度优先搜索(DFS)的方法同样能够解决问题。这种方法通过尝试每一种可能的情况来寻找满足条件的结果。 #### DFS解题思路 在解决此问题时,DFS算法会逐行放置国王,并确保任何两个国王之间不会互相攻击。具体来说: - 使用二进制数表示每一行的态,其中`1`代表当前位置已放置国王,`0`则为空白。 - 对于每一个新的行,在所有未被先前行中的国王威胁的位置上尝试放置新国王。 - 如果当前行的所有列都遍历完毕,则回溯至上一行继续探索其他可能性。 - 当成功放置了指定数量的国王后,计数器加一;如果某次递归达到了最后一行且仍未完成目标,则返回并调整之前的决策。 为了提高效率,还需要提前计算出哪些态是合法的——即不存在连续两位都是`1`的态,这可以通过简单的枚举实现[^4]。 #### Python代码实现 下面是一个基于上述逻辑编写的Python程序片段用于求解该问题: ```python def dfs(row, col_mask, left_diag, right_diag): global n, k, ans if row == n: if sum(bin(col)[2:].count('1') for col in cols) == k: ans += 1 return for i in range(1 << n): if bin(i).count('1') + sum(cols[:row]) > k: continue ok = ((~col_mask & ~left_diag & ~right_diag & (i)) == i) if not ok or '11' in bin(i): continue new_col_mask = col_mask | i new_left_diag = (left_diag | i) << 1 new_right_diag = (right_diag | i) >> 1 dfs(row + 1, new_col_mask, new_left_diag, new_right_diag) n, k = map(int, input().split()) cols = [0]*n ans = 0 dfs(0, 0, 0, 0) print(ans) ``` 这段代码定义了一个名为`dfs()`函数来进行深度优先搜索,它接收四个参数分别表示当前处理的是哪一行(`row`)、当前列上的占用情况(`col_mask`)、左斜线方向上的占用情况(`left_diag`)以及右斜线方向上的占用情况(`right_diag`)。全局变量`n`, `k`用来保存棋盘大小和要放置的国王数目,而`ans`则是最终答案的数量。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值