P2470压缩 题解

这道题来自于 2023/11/18 上午 9:00 由 MikeZ 发起的 DP 专项比赛 T1。这居然是一道 $\color{#9D3DCF} \text{省选}$ 题!(MikeZ 不厚道)

题意简述

这道题的题意比较复杂,有坑。

把一个只有小写字母的字符串压缩,压缩方法:

M 标记重复串的开始,R 重复一遍从上一个 M(如果当前位置左边没有 M,则从串的开始算起)到目前的解压结果。所有的解压结果都不包含 MR

因为这里的 R 只与离它左边最近的 M 对上,所以连续的 M 是无意义的(因为 R 之会与最后的一个 M 对上)。

码前分析

这道题很明显,直接模拟是不可取的,因为你无法保证怎样压缩是最优的。

因为压缩是一段一段的操作,可以理解为区间操作,可以联想到区间 DP。

那 DP 想出来了,怎么设状态呢?

首先,区间 DP 一般设:$dp_{i, j}$ 为区间 i 到 j 的答案。考虑到解压的时候是会让子串直接翻倍,所以有这种转移方法:

  • 如果子串能被从中间分开,且左右两边都完全相等,就可以压缩成一个。
  • 否则枚举一个 $k(i \le k < j)$,把左右两边相加。

但是,有但是哈。因为题目说:R重复从上一个M算起那么就不能产生嵌套关系,否则就会让一些 RM 失效或者效果改变,在上文也说过。

所以我们怎么办呢?我认为最方便的方法是在原来的状态方程上再添一位,变成 $f_{i, j, k}$,那这个 k 只能是 1 或 0,如果是 0 表示 $i \; j$ 区间内不能有 M,反之则有 M

这里还需要搞明白一个非常重要的事情:因为你放 M 进去,是要增加长度的,那这个长度在什么时候加上呢?这里有两种选择。

  1. 当前转移完成了这个状态,马上就加上。

    但是有需要特殊处理的两个情况:

    • 因为 1 的前面默认有个 M,所以得特判
    • 如果当前的子串是可以压缩的(分成两半且两半相等),你要不要压缩呢?如果压缩的话可能会出现嵌套的关系,不可行;如果不压缩的话又有可能丢失了最优解,大亏特亏。
  2. 当前转移完了不加上,在被使用的时候再加上

    这种情况就没啥需要注意的,就是使用的时候不要忘记加上之前没有加上的 M 了。

所以综上所述,我认为用第二种选择更好 (是个人都选第二个)

因此我们假设:推导 $f_{i, j}$ 时,i - 1 处已经有了一个 M。这就是把 M 放在使用时再算上的一个体现。

我们现在来考虑怎么转移状态:

  1. 首先我们考虑可以直接压缩,值得注意的是,只有 $f_{i, j, 0}$ 才可以直接压缩,也就是 i 到 j 这个区间中不允许存在 M,不然就会出现嵌套关系。

  2. 枚举 k 来转移 $f_{i, j, 0}$,这个很容易就得到: $f_{i, j, 0} = \min(f_{i, j, 0}, \; \min(f_{i, k, 0} + j - k))$

    为什么这里是 j - k 而不是 $f_{k + 1, j, 0}$ 呢?是因为 i 到 j 本身是不允许有 M 出现的,i 到 k 这一段能保证 M 就算出现也在 i - 1,而 k + 1 到 j 却不能保证一定没有 M 出现。

  3. 枚举 k 来转移 $f_{i, j, 1}$,$f_{i, j, 1} = \min(f_{i, j, 1}, \min(f_{i, k, 0}, f_{i, k, 1}) + \min(f_{k + 1, j, 0}, f_{k + 1, j, 1}) + 1)$

    这个方程本身是不难理解的,唯一要注意的就是这个 + 1,这个 1 加的是 k 这个位置上的 M,因为上文说了,我们在使用 $f_{i, j}$ 的时候再加上 M如果这里不懂,可以自己想想。

最后输出就是 $\min(f_{1, n, 0}, f_{1, n, 1})$咯。

代码如下:

#include <iostream>
#include <cstring>

using namespace std;

const int N = 55;

int n, f[N][N][2];
char c[N];

bool check(int l, int r)
{
    if ((r - l + 1) % 2 == 1) return false;
    int lens = (r - l + 1) >> 1;
    for (int i = 0; i < lens; i ++ )
    {
        if (c[l + i] != c[l + lens + i]) return false;
    }
    return true;
}

int main()
{
    scanf("%s", c + 1);
    n = strlen(c + 1) ;
    for (int i = 1; i <= n; i ++ ) f[i][i][0] = 1, f[i][i][1] = 2;
    for (int lens = 2; lens <= n; lens ++ )
    {
        for (int i = 1; i + lens - 1 <= n; i ++ )
        {
            int j = i + lens - 1; f[i][j][0] = lens, f[i][j][1] = lens + 1;
            if (check(i, j)) f[i][j][0] = f[i][(j + i) >> 1][0] + 1;
            for (int k = i; k < j; k ++ )
            {
                f[i][j][0] = min(f[i][j][0], f[i][k][0] + j - k);
                f[i][j][1] = min(f[i][j][1], min(f[i][k][1], f[i][k][0]) + min(f[k + 1][j][0], f[k + 1][j][1]) + 1);
            }
        }
    }
    printf("%d\n", min(f[1][n][0], f[1][n][1]));
    return 0;
}
posted @ 2023-11-18 14:52  emo_male_god  阅读(11)  评论(0)    收藏  举报  来源