dp深入与进阶(3):状压dp(2025.02.03)

\(01.\)状压dp概念:

通过将状态压缩为整数(二进制数),从而优化状态转移的方法即为状压dp

而实际使用和操作时可以考虑位运算,还可以在一定程度上加快程序运行

优点:

1.简化复杂状态:用二进制压缩表示复杂状态,如 TSP 问题里城市访问状态

2.降低空间开销:相比传统记录方式,大幅减少状态存储的空间。

3.提升时间效率:利用位运算快速操作状态,减少计算量。

4.应用广泛:适用于棋盘布局、资源分配等多领域问题。

5.实现简便:基于二进制运算与 DP 框架,代码易理解与调试。

(以上内容出自豆包)

\(02.\)状压dp的实现:Luogu P1896 [互不侵犯]

\(i.\)状态压缩

我们以行为单位,对于每一行放/不放王的状态,压缩为二进制01串

0 \(\rightarrow\)该位置不放王

1\(\rightarrow\)该位置放王

例:

对于上图,有状态\(s=(101001000)_2=328\)

如此便可以用二进制数来存储所有的状态

\(ii.\)条件判断

\(Q:\)

对于每次个王,

满足上、下、左、右、左上、左下、右上、右下八个方向上相邻的格子都不能有相邻的王

如何进行这样的判断呢?

\(A:\)

我们假设第\(i\)行状态为\(s_1\),而前一行的状态为\(s_2\)

\(1.\)因为单行内左右不可为王,即要满足((s1<<1)&s1==0)&&((s1>>1)&s1==0) 简写!((s1<<1)|(s1>>1)&s1)

\(2.\)而相邻行不能为有距离大于$\sqrt 2 $的位置为王,即((s1<<1)&s2==0)&&(s1&s2==0)&&((s1>>1)&s2==0) 简写!((s1<<1)|s1|(s1>>1)&s2)

可以把判断满足条件的语句封装为函数\(sta(s1,s2)\),满足条件返回1,不满足返回0,便于调用

\(iii.\)状态转移

(1)状态设计:

考虑f[i][num][s]表示第i行,使用num个棋子,当前状态为s的可行方案数

其中满足\(s_1\)为本行(第i行)的状态,\(s_2\)为上一行的状态

(2)公式推导:

对于所有满足条件的\(s_1\),\(s_2\),所有\(s_2\)的方法数\(s_1\)都满足

\[f(i,num,s_1)=\sum_{sta(s_1,s_2)}f(i-1,num-get_-num(s_1),s_2) \]

其中\(get_-num(s_1)\)函数用来统计\(s_1\)中数字\(1\)的个数

初始化:\(f(0,0,0)=1\),第\(0\)\(0\)个棋子状态为\(0\)的方案为\(1\)

\(iv.\)代码实现

#include<bits/stdc++.h>
using namespace std;
long long f[10][100][2010];

//判断s1,s2是否满足条件  
bool sta(int s1,int s2)
{
    if(((s1<<1)|(s1>>1))&s1)
        return 0;
    if(((s2<<1)|(s2>>1))&s2)
        return 0;
    if(((s1<<1)|s1|(s1>>1))&s2)
        return 0;
    return 1;
}
//计算s二进制中有多少个1
int get_num(int s)
{
    int ret=0;
    while(s)
    {
        ret+=(s&1);//取二进制最后一位的1加入ret
        s>>=1;//s右移一位
    }
    return ret;
}

int main()
{
    int n,k;
    cin>>n>>k;
    f[0][0][0]=1;

	int maxn=1<<n;
    for(int i=1;i<=n;++i)//枚举当前行i
    for(int s1=0;s1<maxn;++s1)//枚举本行状态s1
    for(int s2=0;s2<maxn;++s2)//枚举上一行状态s2
    {
    	if(sta(s1,s2)==0)
            continue;
	    for(int num=0;num<=k;++num)//枚举放了的棋子数
	    {
	        int num_s1=get_num(s1);
	        if(num-num_s1>=0)//判断是否可以进行dp
	            f[i][num][s1]+=f[i-1][num-num_s1][s2];
	    }
    }
    long long ans=0;
    for(int i=0;i<maxn;++i)
        ans+=f[n][k][i];

    cout<<ans;
    return 0;
}
//这个题有更优化的思路 需要进行预处理 感兴趣的可以看XichenOC的题解 
//代码:SamXia

这个题有另一个题解XiChenOC-状压DP(学习笔记)

来自XiChenOC大佬,里面有本题的优化技巧,可以进行学习理解

posted @ 2025-01-31 11:54  SamXia  阅读(140)  评论(0)    收藏  举报