状压dp

状压dp

概述

状压dp就是用二进制表示状态,并且对其子集进行归并的动态规划。它基于基本的位运算,常出现在NOIP和省选中。

状压有明显的数据范围暗示,一般在20左右

三进制枚举

有时我们需要先枚举一个子集,再枚举这个子集的一个子集,此时我们经常二进制枚举两层再判断是否包含。这样很直接,但不如三进制枚举快速。

假设我们有集合\(S\),我们需要\(A\subseteq S\)\(B\subseteq A\),此时\(S\)集合的元素有三种情况:只在\(S\)中;既在\(S\)中又在\(A\)中;同时在三个集合中。只有三种情况,于是我们可以三进制枚举。

三进制枚举模式代码:

for (int x = 0; x < (1 << n); ++x) {
    for (int y = x;; y = (y - 1) & x) {
        for (int i = 0; i < n; ++i) {
            if ((x >> i) & 1) {
                // Do something
            }
            if ((y >> i) & 1) {
                // Do something
            }
        }
        // Do something
        if (y == 0) break;
    }
}

代码中关键的语句是y = (y - 1) & x,-1保证状态不同且最接近原状态&保证是子集。

例题:P1896 [SCOI2005]互不侵犯

题意很简单,就是在\(N\times N\)的棋盘中放\(K\)个国王,使得国王之间不互吃。

我们很容易想到状压dp,我们按行枚举,再枚举上一行的状态,再枚举这一行的状态,再枚举价格,转移即可。

对于两层枚举状态,我们可以使用两层二进制枚举,也可以使用上面的三进制枚举。其中三进制枚举复杂度更低,也不需要检验正确性,其中\(S\)集合为所有状态,\(A\)集合为这一行不会被上一行影响的部分,\(B\)集合为当前行的状态。

这应该是此题除了打表最快的做法了。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF=1e9+7,MAXN=10,MAXP=1<<9,MAXK=85;
int N,K,maxp,ilg[MAXP]/*illegal*/,cnt[MAXP];
LL f[MAXN][MAXP][MAXK],ans;
int main(){
    scanf("%d%d",&N,&K);
    maxp=1<<N;
    for(int i=0;i<maxp;i++){
        for(int j=0;j<N;j++){
            if((i>>j)&1){
                cnt[i]++;
                if(((i>>j)&3)==3)
                    ilg[i]=1;
            }
        }
        f[1][i][cnt[i]]=!ilg[i];
    }
    for(int i=2;i<=N;i++)/*line*/
        for(int j=0;j<maxp;j++){/*last*/
            if(ilg[j])
                continue;
            int no=((maxp-1)^(j|(j<<1)|(j>>1)))%maxp;
            for(int k=no;;k=(k-1)&no){/*current*/
                if(ilg[k])
                    continue;
                if(k>=maxp)
                    return 0;
                for(int l=0;l+cnt[k]<=K;l++)
                    f[i][k][l+cnt[k]]+=f[i-1][j][l];
                if(!k)
                    break;
            }
        }
    for(int i=0;i<maxp;i++)
        ans+=f[N][i][K];
    printf("%lld",ans);
    return 0;
}

枚举排列

对于一个排列,我们有时可以对于它的子集进行操作,此时可以使用状压dp。

但有时我们面对的不是一个排列,而是有重复元素的数列,此时我们需要按照以下公式去重(用多重集合的排列)
\[\frac{n!}{\prod n_i!}\]
其中\(n_i\)为每种元素的个数

例题:P4163 [SCOI2007]排列

转载于:https://www.cnblogs.com/guoshaoyang/p/11296011.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值