bzoj3591 最长上升子序列 [状压dp]

Description:
给出 1 n 1   n 的一个排列的一个最长上升子序列,求原排列可能的种类数。


Solution:
感觉和 bzoj3864 b z o j 3864 很像,可是由于平常写 lis l i s 是用树状数组,并没有想到怎么装压 lis l i s

考虑另一种求 lis l i s 的方法, ai a i 表示长度为 i i lis l i s 结尾最小数字,这样可以通过二分求 lis l i s
很明显 ai a i 是递增的,于是我们考虑装压 ai a i 出现的数字。
于是和 bzoj3864 b z o j 3864 一样,先预处理出 lis l i s 添加一个数字后的下一个状态。
然后枚举当前已经出现过的数字,并从中枚举子集求 ai a i 中出现过的数字,由于 lis l i s 中的数字肯定是出现过的数字的子集,于是我们用三进制表示当前状态。
如何保证给出 lis l i s 一定是原序列的 lis l i s ?我们预处理 pre p r e ,记录一个字符的上一个字符,那么添加新的字符时要求上一个字符出现才能转移,并且最终计算只计算 lis l i s 中有 m m 个元素的状态。
这类题的通用做法是把要求的序列通过自身特点装压并预处理添加字符之后的下一个状态,然后转移即可。
这个代码 A A 不掉,卡常。

#pragma GCC optimize(3)
#include 
using namespace std;
int n, m, last = -1, ans;
int pre[16], bin[16], trans[1 << 16][16], dp[14348910], a[1 << 16], bit[1 << 16];
int main() {
    memset(pre, -1, sizeof(pre));
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; ++i) {
        int x;
        scanf("%d", &x);
        --x;
        pre[x] = last;
        last = x;
    }
    bin[0] = 1;
    for(int i = 0; i < (1 << n); ++i) {
        bit[i] = bit[i >> 1] + (i & 1);
    }
    for(int i = 1; i < n; ++i) {
        bin[i] = bin[i - 1] * 3;
    }
    for(int i = 0; i < n; ++i) {
        if(pre[i] == -1) {
            dp[bin[i] << 1] = 1;
        }
    }
    for(int i = 0; i < (1 << n); ++i) {
        for(int j = 0; j < n; ++j) {
            if(!((i >> j) & 1)) {
                if(bit[i] > m) {
                    continue;
                }
                trans[i][j] = i ^ (1 << j);
                for(int k = j + 1; k < n; ++k) {
                    if(i & (1 << k)) {
                        trans[i][j] ^= (1 << k);
                        break;
                    }
                }
            } else {
                a[i] += bin[j];
            }
        } 
    }
    for(int i = 0; i < (1 << n); ++i) {
        for(int j = 0; j < n; ++j) {
            if(i & (1 << j)) {
                continue;
            }
            if(pre[j] >= 0 && !(i & (1 << pre[j]))) {
                continue;
            }
            for(int k = i; ; k = (k - 1) & i) {
                if(bit[k] > m) {
                    continue;
                }
                dp[a[i ^ (1 << j)] + a[trans[k][j]]] += dp[a[i] + a[k]]; 
                if(!k) {
                    break;
                }
            }
        }
    }
    int ans = 0;
    for(int i = 0; i < (1 << n); ++i) {
        if(bit[i] == m) {
            ans += dp[a[(1 << n) - 1] + a[i]];
        }
    }
    printf("%d\n", ans);
    return 0;
}

你可能感兴趣的:(OJ-bzoj,dp,dp-状态压缩)