寒假思维训练day21

今天更新一道不错的状态压缩DP题,顺带总结一下状态压缩DP。


                                        摘要:

                                               Part1   浅谈状态压缩DP的理解

                                               Part2   浅谈对状态机DP的理解

                                               Part3   关于状态压缩DP的1道例题


Part1  状态压缩DP

1、状态压缩DP:

事物的状态可能包含多个特征,但是事物的状态之间却可以互相转移,此时我们引入状态压缩DP,将事物的复杂的状态用一个数字来替代,此时事物的状态可以用数组的某个位置表示,从而可以进行状态的转移。

2、常见的状态表示:

(1) 用10进制数字本身表示状态,比如表示当前状态%某个数字的余数等等,这里举一个例子。
(2) 用10进制内蕴含的二进制位表示状态, 01, 表示了每个位置上的两种状态,它既可以表示是否存在,也可以表示数量的奇偶性。

(3) 用10进制内蕴含的K进制(除了10进制和2进制外的其它进制)表示状态,这种题我没见过,但是基于上面我们很容易可以推广。

3、什么时候我们可以用:

首先是在你的状态表示基础上,整体的转移图是一个拓扑图,也就是可以通过递推得来,并且时间空间可以过得去,此时我们就可以用状态压缩DP。


Part2 状态机DP

我在之前写过一篇关于状态机DP的文章,里面有详细的理论和几道很好的例题:

http://t.csdnimg.cn/POtFs


Part3  例题: 小红的回文数

题目链接:E-小红的回文数_牛客周赛 Round 32 (nowcoder.com)

(1)题意:

小红定义一个整数是“好数”,当且仅当该整数通过重排之后可以形成回文数。(可以包含前导零)现在小红拿到了一个正整数x,小红想截取一段连续区间得到好数,她想知道有多少种不同的方案? 1<= x <= 10^{10^5}


寒假思维训练day21_第1张图片

(2)题解: 

暴力显然会超时,必定需要n * n 的复杂度,此时我们不妨考虑一下DP,  我们此时从左到右去考虑这个数,我们考虑每个以第i位数结尾的情况,最后答案就是累加后的值,我们此时考虑一下以第i位数结尾的区间,我们发现对于一个数字而言,每位的数字只能是0-9的数字之一,我们不妨用10个二进制位表示每一种数字的数量%2是多少,这样我们就可以通过统计1的个数判断有几个奇数,如果要构造一个回文串,显然只能由一个或者0个奇数的位。
状态转移方程:F[i][state],表示以第i位数结尾的数字区间,且0-9各个的数字情况是state的方案数,F[i][state1] = F[i - 1][state1 \oplus (1 << a[i])],并且对于每个位单独一位的情况也要考虑,所以状态转移代码是: 

    vector> dp(n + 1, vector((1 << 10) + 2)); 
    dp[0][0] = 1;  
    for(int i = 1; i <= n; i ++ ) {
        for(int j = 0; j < (1 << 10); j ++ ) {
            int k = j ^ (1 << (s[i - 1] - '0'));
            dp[i][k] += dp[i - 1][j]; 
        }
        if(i >= 2) dp[i][1 << (s[i - 1] - '0')] ++; 
    }

你以为事情结束了吗,这是一道比较毒瘤的题,它会卡你的空间,在此基础上我们需要引入滚动数组优化,优化掉一维的空间。
此时的转移代码是:

    dp[0] = 1;  
    long long res = 0; 
    for(int i = 1; i <= n; i ++ ) {
        memset(usdp, 0, sizeof usdp); 
        for(int j = 0; j < 1 << 10; j ++ ) 
            usdp[j ^ (1 << (s[i - 1] - '0'))] += dp[j];
        if(i >= 2) usdp[1 << (s[i  - 1] - '0')] ++; 
        for(int j = 0; j < 1 << 10; j ++ ) {
            cnt = 0; 
            for(int c = 0; c <= 9; c ++ ) 
                if(j >> c & 1) ++ cnt; 
            if(cnt <= 1) res += usdp[j]; 
            dp[j] = usdp[j]; 
        }
    }
    cout << res << endl; 

(3) 代码 (C ++):

滚动数组优化:
 

#include 
// #define int long long 
#define lowbit(x) (x&-x)
using namespace std; 
const int N = 1e5 + 2; 
const int inf = 0x3f3f3f3f; 
int n, cnt; 
long long dp[2025], usdp[2025];  
// int a[N]; 
void solve() {
    string s; 
    cin >> s; 
    n = s.size();
    cnt = 0; 
//     for(int i = 1; i <= n; i ++ ) a[i] = s[i - 1] - '0';  
    dp[0] = 1;  
    long long res = 0; 
    for(int i = 1; i <= n; i ++ ) {
        memset(usdp, 0, sizeof usdp); 
        for(int j = 0; j < 1 << 10; j ++ ) 
            usdp[j ^ (1 << (s[i - 1] - '0'))] += dp[j];
        if(i >= 2) usdp[1 << (s[i  - 1] - '0')] ++; 
        for(int j = 0; j < 1 << 10; j ++ ) {
            cnt = 0; 
            for(int c = 0; c <= 9; c ++ ) 
                if(j >> c & 1) ++ cnt; 
            if(cnt <= 1) res += usdp[j]; 
            dp[j] = usdp[j]; 
        }
    }
    cout << res << endl; 
    
}
int main() {
    ios::sync_with_stdio(false); 
    cin.tie(0); 
    cout.tie(0); 
    int ts = 1; 
//     cin >> ts; 
    while(ts -- ) solve(); 
    
    return 0; 
}

你可能感兴趣的:(算法,动态规划)