首页 > 其他分享 >CF2045H - Missing Separators 题解

CF2045H - Missing Separators 题解

时间:2024-12-07 14:35:06浏览次数:7  
标签:last int 题解 CF2045H Separators 单词 ldots dp 字典

CF2045H - Missing Separators

题面

您有一本字典,它是按字母顺序排列的多个单词的列表。每个单词都由大写英文字母组成。

您想打印这本字典。然而,打印系统出现了一个错误,列表中的所有单词都紧挨着打印,单词之间没有任何分隔符。现在,您最终得到的字符串 \(S\) 是字典中所有单词按照所列顺序连接而成的。

您的任务是将 \(S\) 拆分为一个或多个单词,从而重建字典。请注意,重建后的字典必须由按字母顺序排列的不同单词组成。此外,您还要最大限度地增加字典中的单词数量。如果有多部字词数最多的词典,您可以任选一部。


题解

这场比赛全是神仙题,orz。

考虑设计 \(dp\) 数组,\(dp[x][y]\) 表示将 \(S[x \ldots y]\) 分割成为一个单词的最多单词数。

可以发现 \(dp[x][y]\) 向 \(dp[y + 1][z]\) 的转移当且仅当 \(S[x \ldots y]\) 的字典序比 \(S[y + 1 \ldots z]\) 的字典序小。

唯一的问题是,这个 \(dp\) 方式是 \(O(n^3)\) 的,我们不能接受,因此我们考虑找到一个关键点 \(z\),使得 \(S[x \ldots y]\) 的字典序比 \(S[y + 1 \ldots z]\) 的字典序小,那么 \(dp[y + 1][z + k](k \ge 0)\) 的情况可以由前者覆盖。

我们考虑这个点 \(z\) 如何寻找,可以发现我们可以求出 \(S[x \ldots y]\) 与 \(S[y + 1 \ldots]\) 的最长公共前缀,他们具有最长公共前缀的长度为 \(k\),显而易见这个 \(k\) 即是我们所求的 \(z\)。

我们考虑如何求出 \(S[x \ldots y]\) 与 \(S[y + 1 \ldots]\) 的最长公共前缀 \(LCPS[i][j]\),我们可以倒序枚举 \(S[i\ldots]\) 和 \(S[j\ldots]\),如果 \(S[i] = S[j]\),那么 \(LCPS[i][j] = LCPS[i + 1][j + 1] + 1\),这样我们可以预处理出任意后缀的最长公共前缀了。

接下来考虑转移,对于一个位,首先可以继承前者状态 \(dp[i][j] = dp[i][j - 1]\),往分割后的字符串后加上一个字符不改变数量。另一方面,对于具有最长公共前缀长度为 \(k\) 的两个相邻串 \(dp[x][y]\) 可以转移到 \(dp[y + 1][y + 1 + k]\),因为 \(S[x \ldots y]\) 比 \(S[y + 1 \ldots y + 1 + k]\) 短,所以字典序一定更小。在转移中记录一下前一字符串的开头即可,方便后续输出答案。

时间复杂度 \(O(\vert S \vert^2)\)。

参考代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5010;
char s[N];
int dp[N][N], LCPS[N][N];
int pre[N][N];

int main() {
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    for (int i = n; i; i -- ) {
        for (int j = n; j; j -- ) {
            if (s[i] == s[j]) LCPS[i][j] = LCPS[i + 1][j + 1] + 1;
            dp[i][j] = -1e9;
        }
    }
    dp[1][0] = 1;
    for (int i = 1; i <= n; i ++ ) {
        for (int j = i; j <= n; j ++ ) {
            if (dp[i][j - 1] > dp[i][j]) {
                dp[i][j] = dp[i][j - 1];
                pre[i][j] = pre[i][j - 1];
            }
            if (j == n) continue;
            int k = min(j - i + 1, LCPS[i][j + 1]);
            if (j + 1 + k > n) continue;
            if (i + k > j || s[i + k] < s[j + 1 + k]) {
                if (dp[i][j] + 1 > dp[j + 1][j + 1 + k]) {
                    dp[j + 1][j + 1 + k] = dp[i][j] + 1;
                    pre[j + 1][j + 1 + k] = i;
                }
            }
        }
    }
    int cnt = 0, last = 0, ed = n;
    for (int i = 1; i <= n; i ++ )
        if (dp[i][n] > cnt)
            cnt = dp[i][n], last = i;
    vector<int> ans(1, n + 1);
    while (last) {
        int t = pre[last][ed];
        ans.push_back(last);
        ed = last - 1, last = t;
    }
    reverse(ans.begin(), ans.end());
    cout << cnt << "\n";
    for (int i = 0; i + 1 < ans.size(); i ++ , cout << "\n")
        for (int j = ans[i]; j < ans[i + 1]; j ++ )
            cout << s[j];
    return 0;
}

标签:last,int,题解,CF2045H,Separators,单词,ldots,dp,字典
From: https://www.cnblogs.com/YipChipqwq/p/-/CF2045H

相关文章

  • 蓝桥杯 2024 省赛 C++ B组 R 格式 (JAVA面向对象 高精度 纯api题解)
    解题思路:由于数位较大这里采用高精度,又因为高精度写起来比较麻烦所以这里直接采用JAVAapi中的高精度浮点数类型和高精度整数类型,应为高精度浮点数类型四舍五入较为麻烦所以这里改为手动四舍五入importjava.math.BigDecimal;importjava.math.BigInteger;importjava.util......
  • 接龙数列(第十四届蓝桥杯C++B组JAVA题解 动态规划)
    对于一个长度为 KK 的整数数列:A1,A2,...,AK,我们称之为接龙数列当且仅当 Ai 的首位数字恰好等于 Ai−1 的末位数字(2≤i≤K)。例如 12,23,35,56,61,11是接龙数列;12,23,34,56不是接龙数列,因为 56的首位数字不等于 3434 的末位数字。所有长度为 11 的整数数列都......
  • 题解:AtCoder Beginner Contest AT_abc380_d ABC380D Strange Mirroring
    题目大意给定一个字符串$S$,执行$10^{100}$次以下操作:首先,令字符串$T$为字符串$S$中所有大写字母变为小写字母,小写字母变为大写字母的结果。其次,将$T$拼接在$S$后面。接下来,有一些询问:请输出在所有操作执行完成之后$S$的第$K$个字母。思路乍一看,好大的数......
  • 题解:AtCoder Beginner Contest AT_abc373_d ABC373D Hidden Weights
    题目传送门题目翻译给你一个$N$个点,$M$条边的有向图,其中边有边权。现在让你给每一个点设置一个点权$a$,使得对于任意两点$x$和$y$,如果$x$到$y$有一条边,边权为$w$,那么需要满足$a_y-a_x=w$。现在让你输出一组合法的分配方案,题目保证存在,输出任意一组都行。思路1(注意......
  • 题解:[USACO07DEC] Sightseeing Cows G
    洛谷P2868题目大意有个$n$个点,$m$条边的有向图,点有点权,边有边权。现在要找出一个环,使得点权和与边权和的比值最大。思路既然说要使得点权和与边权和的比值最大,那么就会想到$01$分数规划。二分答案就不用说了,重点是这个$check$函数。$01$分数规划的板子中要检查的是......
  • 题解:AT_abc371_c [ABC371C] Make Isomorphic
    题目大意有两个简单无向图,你每一次可以给第二个图添上或去掉一条边,有相应花费,问将两个图变为同构最少需要花费多少钱。思路观察数据范围,可以发现$N$非常小,可以考虑枚举全排列。所以我们就暴力枚举$1$到$N$,把这个当前排列记在一个数组里,$t[i]$表示在第一个图中点$i$对应......
  • 题解:AT_abc366_d [ABC366D] Cuboid Sum Query
    这是一个区间求和问题,因为Q很大,所以使用前缀和。N不超过100,所以在Q次询问中嵌套一次O(n)的循环是不会超时的。令s[i][j][k]表示第i层中左上角为(1,1),右下角为(j,k)的矩形中所有元素的和。s[i][j][k]=s[i][j-1][k]+s[i][j][k-1]-s[i][j-1][k-1]+a[i][j][k];然后在Q次询问中,枚举层数......
  • 题解:AT_abc369_e [ABC369E] Sightseeing Tour
    题目大意给定一个$N$个点,$M$条边的无向图。其中边有边权。有$Q$次询问,每一次给你$K$条必须经过的边(但是方向没有限制),问从$1$到$N$的最短路长度是多少。思路观察数据范围,可以发现:虽然$M$很大,但是$N$和$K$并不大。$K\le5$,可以暴力枚举每一条边经过时的方向以及......
  • 题解:CF1950F 0, 1, 2, Tree!
    题目链接思路不能形成树的情况:第一,一棵树必须有叶子节点。所以$c=0$的情况就一定不能形成一棵树。其次,可以发现,我们每增加一个度为$2$的节点,叶子节点就也会增加$1$个。所以$a+1\neqc$的情况也肯定不行了。代码片段if(!c||a+1!=c) cout<<"-1"<<endl;......
  • 题解:P4009 汽车加油行驶问题
    题目思路这是一个分层图最短路问题,我们可以使用升维的方法来完成本题。因为存在加油付费的问题,边权不一定为$1$,所以不能使用广搜来做。数据范围不大:$N\le100$。可以使用SPFA算法完成本题。每一个状态有三个值,分别是当前到达的行、列,以及剩下的油还能走几步。考虑是否需要加油......