SCOI2005 互不侵犯 - 状压DP

49 篇文章 0 订阅
6 篇文章 0 订阅

考虑如何划分阶段,如何确定决策及转移
阶段就是子问题的规模,对于这道题,可以说“更小的棋盘,更少的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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值