状压DP——子集DP

题面

我们直接通过一个例题来了解子集 DP,子集 DP 属于状压 DP 的一种。

给定一个长度不超过 n 的字符串 s,如果 s 中的一个子序列是回文,那么我们就可以从 s 中移除这个子序列,求最少经过多少步我们可以移除整个字符串 s。如:我们可以从"dqewfretd"中移除 “defed”,剩下的字符串即:“qwrt”。

题目解析

使用状压DP进行求解:
对于状态i,用 d p [ i ] dp[i] dp[i]表示最少的操作次数
当状态i
对应的子序列是回文时,dp[i]=1
对于状态i的一个子状态t,如果t也是回文序列,那么
d p [ i ] = m i n ( d p [ i ] , d p [ i ⊕ t ] + 1 ) dp[i]=min(dp[i],dp[i⊕t]+1) dp[i]=min(dp[i],dp[it]+1)
O ( 4 n + n × 2 n ) \mathcal{O}(4^n + n\times 2^n) O(4n+n×2n)
子集 dp 有一个巧妙的写法,把时间复杂度压缩到 O ( 3 n + n × 2 n ) O(3^n + n\times 2^n) O(3n+n×2n)一般来说 3 n 3^n 3n会远大于 n × 2 n , 所 以 子 集 d p 的 复 杂 度 用 n\times 2^n,所以子集 dp 的复杂度用 n×2ndp O(3^n)$

for (int i = 1; i < (1 << n); i++) {
    dp[i] = IsPalindrome(i) ? 1 : inf;  // 判断当前状态是否是回文,如果是回文则步骤数为 1
    for (int t = i; t; t = (t - 1) & i) {
        dp[i] = min(dp[i], dp[t] + dp[i ^ t]);
    }
}
cout << dp[(1 << n) - 1] << endl;

通过

for (int t = i; t; t = (t - 1) & i)

这个方式我们可以快速枚举一个状态的所有子集。

示例代码

#include <iostream>
#include <string>
using namespace std;
int dp[1 << 16];
int n;
string str;
bool IsPalindrome(int x){
    string ss="";
    int cnt=0;
    for(int i=0;i<n;i++){
        if(x&(1<<i)){
            ss+=str[i];
            cnt++;
        }
    }
    for(int i=0,j=cnt-1;i<j;i++,j--){
        if(ss[i]!=ss[j]){
            return false;
        }
    }
    return true;
}
int main() {
    cin >> n;
    cin >> str;
    for(int i=1;i<(1<<n);i++){
        dp[i]=IsPalindrome(i) ? 1 : n;
        for(int t=i;t;t=(t-1)&i){
            dp[i]=min(dp[i],dp[t]+dp[i^t]);
        }
    }
    cout<<dp[(1<<n)-1]<<endl;
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值