洛谷 P3736 [HAOI2016]字符合并【动态规划】【状态压缩】

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目连接:洛谷 P3736

题目大意:给定长度为 n n 01 01 字符串,每次选取连续长度为 k k 的子串进行合并,每种合并都会得到新的 01 01 字符,并且会得到分数。求合并后的最大分数。

题目分析:

读完题就发现这道题是一道区间 D P DP (因为每次消去一段后其实就是一个区间问题),对于传统的区间 D P DP (比如 N O I P NOIP 能量),通常设立的状态形式如下:

f ( I , J ) f(I,J) 表示把区间 [ I , J ] [I,J] 消去(或者其他操作)能够得到的最大得分,但是这道题不能这样设立状态,因为我们每次消去长度为K的区间后还剩下了一个数,也就是将连续 K K 个数变为了 1 1 个数,所以我们类似设立状态:

f ( I , J , K ) f(I,J,K) 表示区间 [ I , J ] [I,J] 消除后最后得到的二进制状态为 K K 时的最大得分。

我们先考虑用区间 D P DP 的方法进行状态转移,也就是枚举区间的断点,假设断点为 m i d mid ,我们就枚举 i m i d j i\leq mid\leq j 就可以了,但是这里要注意的地方在于,我们每次消除的区间长度固定,也就是 k k ,那么我们对于 m i d mid 的要求也是有要求。除去枚举断点 m i d mid ,显然我们还需要枚举一个二进制状态,通过这个二进制状态来进行转移,我们假设此处枚举的二进制状态为 l l ,那么此处的状态转移方程为:

f ( i , j , l < < 1 ) = m a x ( f ( i , j , l < < 1 ) , f ( i , m i d 1 , l ) + f ( m i d , j , 0 ) ) f(i, j, l<<1) = max(f(i,j,l<<1),f(i,mid-1,l)+f(mid,j,0))

f ( i , j , l < < 1 1 ) = m a x ( f ( i , j , l < < 1 1 , f ( i , m i d 1 , l ) + f ( m i d , j , 1 ) ) ) f(i, j, l<<1|1) = max(f(i,j,l<<1|1,f(i,mid-1,l)+f(mid,j,1)))

但是这样的状态转移似乎有点小问题,我们实际上并没有一个对于区间本身消除的得分变化,这个时候我们就将长度正好可以消除的二进制状态 l l 直接向 c [ l ] c[l] 转移,在这里我们用一个临时数组记录即可,大家可以结合状态转移方程理解一下:

g [ c [ x ] ] = m a x ( g [ c [ x ] ] , f ( i , j , l ) + w [ l ] ) g[c[x]] = max(g[c[x]],f(i,j,l)+w[l])

f ( i , j , 0 ) = g [ 0 ] , f ( i , j , 1 ) = g [ 1 ] f(i,j,0)=g[0],f(i,j,1)=g[1]

参考代码:

#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;

const ll inf = 1e9;
ll ch[305];
ll n, k, num, ans;
ll c[(1 << 16) + 5], w[(1 << 16) + 5], f[305][305][(1 << 8) + 5];

int main(){
    scanf("%lld%lld", &n, &k);
    num = (1 << k) - 1;
    for (ll i = 1; i <= n; ++i) {
        scanf("%lld", &ch[i]);
    }
    for (ll i = 0; i <= num; ++i) {
        scanf("%lld%lld", &c[i], &w[i]);
    }
    for (ll i = 1; i <= n; ++i) {
        for (ll j = 1; j <= n; ++j) {
            for (ll l = 0; l <= num; ++l) {
                f[i][j][l] = -inf;
            }
        }
    }
    for (ll i = n; i >= 1; --i) {
        for (ll j = i; j <= n; ++j) {
            if (i == j) {
                f[i][j][ch[i]] = 0;
                continue;
            }
            ll length = j - i;
            while (length >= k) {
                length -= k - 1;
            }
            for (ll mid = j; mid >= i + 1; mid -= k - 1) {
                for (ll l = (1 << length) - 1; l >= 0; --l) {
                    if (f[i][mid - 1][l] == -inf) {
                        continue;
                    }
                    if (f[mid][j][0] != -inf) {
                        f[i][j][l << 1] = max(f[i][j][l << 1], f[i][mid - 1][l] + f[mid][j][0]);
                    }
                    if (f[mid][j][1] != -inf) {
                        f[i][j][l << 1 | 1] = max(f[i][j][l << 1 | 1], f[i][mid - 1][l] + f[mid][j][1]);
                    }
                }
            }
            if (length == k - 1) {
                ll g[2] = {-inf, -inf};
                for (ll l = num; l >= 0; --l) {
                    if (f[i][j][l] != -inf) {
                        g[c[l]] = max(g[c[l]], f[i][j][l] + w[l]);
                    }
                }
                f[i][j][0] = g[0];
                f[i][j][1] = g[1];
            }
        }
    }
    for (ll i = 0; i <= num; ++i) {
        ans = max(ans, f[1][n][i]);
    }
    printf("%lld", ans);
    return 0;
}
复制代码

Supongo que te gusta

Origin juejin.im/post/7074823360437288990
Recomendado
Clasificación