欲望以提升热忱,毅力以磨平高山!|

XichenOC

园龄:1个月粉丝:4关注:0

2025-01-24 15:20阅读: 34评论: 0推荐: 1

状压DP(学习笔记)

简介:

状压 \(dp\) 很明显是将状态压缩后进行 \(dp\),这种算法多用于状态只有两种的情况,且一般给定范围较小,如 \(n \leq 16\) 等,遇到这种情况就可以考虑去状压 \(dp\)

前置知识:

我们知道一个数可以表示成二进制,如 \((25)_{10}=(1101)_2\) 那我们就可以将一个区间内所有的状态表示成二进制,然后在转换成十进制,这样就将一个区间内的状态压缩成一个数字。再由这个状态进行转移。

实现

这里给一道例题来看看状压 \(dp\) 的实现过程:
P1896 [SCOI2005] 互不侵犯

阅读完题目可以发现其,棋盘大小较小,那很明显是状压 \(dp\),那我们就将一行的状态给压缩起来,即在二进制中对应位就是在该列对应的位置,若这个位置上有棋那就为 \(1\) 反之为 \(0\) 那这样,就储存下来了。

那我们再来看看这些状态有什么限制:

  • 首先根据题意可以知道,同一行的棋子不能相邻,即其左右都不能有棋子,对应到二进制就是一个 \(1\) 左移一位和右移一位都不为 \(1\),那么我们可以用二进制的运算法则来看就相当于 (x<<1)&x==0 && (x>>1)&x==0,因为与运算要求是都为 \(1\) 才返回 \(1\) 那如果其左右两位返回的不是零,那很明显其左右两位有 \(1\) 就不符合要求。上面的关系还能写成 !(((x<<1)|(x>>1))&x)
  • 其次还要判断其是否在上面棋子的禁止范围内,那我们令 \(s1,s2\)分别代表当前行的状态和上一行的状态,那同理(s1<<1)&s2==0 && (s1>>1)&s2==0 && s1&s2==0。即左移一位,右移一位和不移都与上一行没有重复的 \(1\),同样可以写成!(((s1<<1)|(s1>>1)|s1)&s2),这样就可以在枚举时判断是否满足这两条性质,再来进行转移。

状态转移:

转移方程: 我们假定 \(dp[x][y][z]\) 表示第 \(x\) 行,用了 \(y\) 个棋子,当前行的状态为 \(z\) 的方案数。那很显然要先枚举行数 \(i\),在枚举当前行的状态 \(s1\) 与上一行的状态 \(s2\),最后在枚举用的棋子数 \(len\),那如果满足限制的前提下转移方程就为

\[dp[i][len][s1]+=dp[i-1][len-cnt[s1]][s2] \]

其中 \(cnt[s1]\)表示当前状态的所用棋子数。

初始化: 而对于第 \(0\) 行,用了 \(0\) 个棋子,状态为 \(0\) 的方案数为 \(1\),即:

\[dp[0][0][0]=1 \]

统计答案: 只需要统计最后一行,用完棋子的所有状态的方案数之和即可。

优化:

对于性质 \(1\)\(cnt[s1]\) 可以提前预处理出来。
暴力枚举所有的状态,对于每个状态都统计一下 \(1\) 的个数,即 \(cnt[s1]\)。然后看他是否满足性质 \(1\),若满足,就加入 \(ok[++num]\) 中,方便后面调用:

完整代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2010;
int ok[N],cnt[N];
int dp[10][100][N];
signed main(){
    int n,m;
    scanf("%lld%lld",&n,&m);
    int num=0;
    for(int i=0;i<(1<<n);i++){
        int tot=0;
        int s=i;
        while(s){
            if(s&1)tot++;
            s>>=1;
        }
        cnt[i]=tot;
        if(!(((i<<1)|(i>>1))&i)){
            ok[++num]=i;
        }
    }
    dp[0][0][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=num;j++){
            for(int k=1;k<=num;k++){
                int s1=ok[j],s2=ok[k];
                if(!((s2|(s2<<1)|(s2>>1))&s1)){
                    for(int len=0;len<=m;len++){
                        if(len-cnt[s1]>=0){
                            dp[i][len][s1]+=dp[i-1][len-cnt[s1]][s2];
                        }
                    }
                }
            }
        }
    }
    int ans=0;
    for(int i=1;i<=num;i++){
        ans+=dp[n][m][ok[i]];
    }
    printf("%lld",ans);
}

例题:

1.P2704 [NOI2001] 炮兵阵地 题解
2.P1171 售货员的难题 题解
3.P1879 [USACO06NOV] Corn Fields G 题解
4.P3092 [USACO13NOV] No Change G 题解

本文作者:XichenOC

本文链接:https://www.cnblogs.com/XichenOC/p/18689567

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   XichenOC  阅读(34)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起