CF1537E2 Erase and Extend (Hard Version) 题解

一、题目:

codeforces原题

洛谷原题

二、思路:

这题不知道为什么官方题解写得那么复杂。还需要用到扩展 KMP,即 Z 函数。感谢 Robert_JYH 巨佬提供的思路,让我明白了一种非常简单的做法。

首先,我们可以证明,最优解一定是通过先执行操作 1,后执行操作 2 来得到的(当然也可以不进行操作 1,直接进行操作 2)。因为如果最优解是执行了一些操作 2 之后,再执行的操作 1,那么我们发现把操作 1 放到操作 2 之前操作一定会使答案变得不劣

所以问题就转化成给定一个字符串 \(s\),需要保留 \(s\) 的一个前缀 \(pre\),同时让 \(pre\) 复制若干次,得到一个长度为 \(k\) 的字符串(多余的部分删去)。目标是让最终的字符串的字典序最小。

暴力当然很简单了,我们枚举所有前缀 \(pre\),模拟这个过程,更新答案即可。你就可以成功地解决这道题的简单版了。

那么现在来考虑线性的做法。

我们设当前最优的前缀为 \(s[0\sim p-1]\),现在枚举到的前缀是 \(s[0\sim i]\)。那么分为三种情况。(字符串的下标从 0 开始。)

  1. \(s[i]>s[i\bmod p]\),那么非常遗憾,\(i\)\(i\) 以后的所有前缀均不能作为最优答案。直接跳出循环。
  2. \(s[i]<s[i\bmod p]\),我们发现用前缀 \(s[0\sim i]\) 比前缀 \(s[0\sim p-1]\) 更优,于是令 \(p\gets i+1\)
  3. \(s[i]=s[i\bmod p]\),这并不能说明什么,我们只能什么都不做。

然后呢?然后你就会惊奇的发现,你写完了一道 Div2 难度的第六题!

三、代码:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
#define FILEIN(s) freopen(s".in", "r", stdin);
#define FILEOUT(s) freopen(s".out", "w", stdout)
#define mem(s, v) memset(s, v, sizeof s)

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

int n, K;
string s;

int main() {
    n = read(); K = read();
    cin >> s;
    int p = 1, i = 0, n = s.length();
    for (; i < n; ++ i) {
        if (s[i] > s[i % p]) break;
        if (s[i] < s[i % p]) p = i + 1;
    }
    for (i = 0; i < K; ++ i) putchar(s[i % p]);
    puts("");
    return 0;
}

posted @ 2021-06-19 13:12  蓝田日暖玉生烟  阅读(92)  评论(0编辑  收藏  举报