状态压缩DP

关于状态表示形式的优化方式

使用场景:需要记录不超过二进制数位(通常22位以内)的bool信息的DP问题。

常见的位操作

简单操作

  1. 任何二进制数位 &1 得到它本身。

  2. 任何二进制数位 ^1 则取反。

  3. 任何二进制数位 &0 则赋值为0。

  4. 任何二进制数位 |1 则赋值为1。

混合操作

  1. (n>>k&1) 取出二进制下n的第k位(从右往左)。

  2. n&((1<<k)-1) 取出二进制下n的右k位。

  3. n^(1<<k) 将二进制下n的第k位取反。

  4. n|(1<<k) 将二进制下n的第k位赋值为1。

  5. n&(~(1<<k)) 将二进制下n的第k位赋值为0。

题目

海贼王的伟大航路

题面

从1节点开始不重复进过每一个点到达n节点,从i到j的时间是t[i][j],求最小时间。

状态

dp[i][j]表示到达点 j时经过的点的状态为二进制下的i的最少时间。

答案:cout<<dp[(1<<n)-1][n-1];

状态转移

for(int i=0;i<(1<<n);i++)//经过的状态

    for(int j=0;j<=n-1;j++)//当前到达的点j

        if((i>>j)&1)

            for(int k=0;k<=n-1;k++)//枚举上一个点k

                if((i>>k)&1)

                    dp[i][j]=min(dp[i][j],dp[i^(1<<j)][k]+t[k][j]);

初始状态

dp[1][0]=0,其余为极大值。

P1879 [USACO06NOV] Corn Fields n

状态

dp[i][j]表示第i行种草状态为二进制下的j的方案数。

答案:

∑i=0(1<<n)−1dpm,i\sum\limits_{i=0}^{(1<<n)-1}dp_{m,i}i=0(1<<n)1dpm,i

状态转移

for(int i=1;i<=m;i++)//行

    for(int j=0;j<(1<n);j++)//i行种草的状态

        for(int k=0;k<(1<n);k+)//i-1行种草

            if(check(i,j)==true)//肥沃、上左右不冲突

                dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;

初始状态

dp[0][0]=1。

check

check(i,j)保证第i行的草必须在肥沃的土地。

  1. if((j&soil[i])==j)则种草合法;

  2. if((j&k)==0)则上下不冲突;

  3. if((j&(j<<1))==0),则左右不冲突。

P2704 [NOI2001] 炮兵阵地

状态

dp[i][j][k]表示前i行且第i行放置炮兵的状态为二进制下的j,第i-1行为二进制下的k时的最多炮兵数量。

答案:

∑i=0(1<<n)−1∑j=0(1<<n)−1dpn,i,j\sum\limits_{i=0}^{(1<<n)-1}\sum\limits_{j=0}^{(1<<n)-1}dp_{n,i,j}i=0(1<<n)1j=0(1<<n)1dpn,i,j

状态转移

for(int i=1;i<=n;i++) //行

    for(int j=0;j<(1<<m);j++) //第i行炮兵的状态

    {

        if(/*j二进制下的1有放在高地*/) continue;

        if(/*j左右发现连续3个位置有冲突*/) continue;

        for(int k=0;k<(1<<m);k++) //第i-1行状态

            for(int p=0;p<(i<<m);p++) //第i-2行状态

            {

                if((k&j)||(p&j)) continue;

                dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][p]+__builtin_popcount(j));

            }

    }

初始状态

dp[0][0][0]=0,其余memset。

优化

将有用的状态预处理,既可优化时间,也可优化空间。

P1896 [SCOI2005] 互不侵犯

状态

dp[i][j][p]代表前i行且第i行放置的棋子状态为二进制下的j,总棋子已经放入p个的方案数。

答案:

∑i=0(1<<n)−1dpn,i,K\sum\limits_{i=0}^{(1<<n)-1}dp_{n,i,K}i=0(1<<n)1dpn,i,K

状态转移

for(int i=1;i<=n;i++) //行

    for(int j=0;j<(1<<n);j++) //棋子状态

        for(int x=0;x<(1<<n);x++) //上一行棋子状态

            for(int p=0;p<=K;p++) //总棋子数量

            {

                if((x&j)|(x<<1&j)|(x>>1&j)|(j<<1&j)) continue;

                dp[i][j][p+__builtin_popcount(j)]+=dp[i-1][x][p];

            }

初始状态

dp[0][0][0]=1;


也可使用上一题的优化。

P3092 [USACO13NOV] No Change G

状态

dp[j]表示硬币使用状态为二进制下的j时购买到的最大物品编号。

答案:

∑i=1n{ans=min⁡(ans,count(i))dpi==nans=anselse\sum\limits_{i=1}^n \begin{cases} ans=\min(ans,count(i)) & dp_i==n \\ ans=ans & else \end{cases}i=1n{ans=min(ans,count(i))ans=ansdpi==nelse//count(i) 代表状态i对应的硬币面值之和

cout<<sum-ans。//sum代表总钱数

状态转移

for(int i=0;i<(1<<k);i++) //枚举硬币使用状态

    for(int j=0;j<k;j++) //枚举当前使用的硬币

        if(i>>j&1) //硬币j可以使用

            dp[i]=max(dp[i],erfen(dp[i^(1<<j)],a[j])); //erfen代表二分

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值