1 条题解
-
0
这题的题解多为远古题解,故读起来多有误解与盲区,因此写了这篇题解。
题目简述
给定你要印刷的文本串 。
你决定刻一个印章,印章每使用一次,就会将印章上的所有字母印到纸上。
同一个位置的相同字符可以印多次。例如:用
aba这个印章可以完成印制ababa的工作(中间的a被印了两次)。但是,因为印上去的东西不能被抹掉,在同一位置上印不同字符是不允许的。例如:用aba这个印章不可以完成印制abcba的工作。你希望印章的字符串长度尽可能小。
思路
以下是我们的约定 & 发现:
-
可以被 印刷,当且仅当可以通过若干次印印章 得到一字符串 ,使得 是 的子串。
-
正好被 印刷,当且仅当可以通过若干次印印章 得到一字符串 ,使得 。
-
表示 。
-
是 的 当且仅当 既是 的前缀,也是 的后缀,可以发现若 正好被印刷必有印章 是其的 。
-
是 的真 当且仅当 既是 的前缀,也是 的后缀,且 ,同时定义 表示 的最长真 长度。
-
字符串 的任意真 一定是 最长真 的 。(有点绕,可以画下图,这里简单解释一下,其实就是 KMP 的一些思想)
-
由上一条可以推广到对于任意的印章 ,若 是 的真 ,且正好印刷 ,则 也可以正好印刷 的最长真 。(解释)
考虑 DP,令 表示将 正好印刷所需的最小印章长度。
可以发现, 只可能等于 或 , 很显然,直接令印章 即可,而 可以用上述中的第 条理解。
但不是何时都能等于 ,又是那个例子,假设 长 ,形如这样:
#######__________#######。可以发现,中间有 个其他字符,如果印章长度只有 可能印刷不到,那么何时才能取 呢?
而只要我们把中间一段覆盖到就可以取到 ,就比如这样:
#######__________####### ####### $$$$$$$$$$$$$$$$$ 第一种:正好与后面一段的 Border 衔接 $$$$$$$$$$$$$$$$$$$$ 第二种:与后面一段的 Border 有交集即需存在 ,使得 (显然 与 使用的印章要相同吧?) 且 ,实现时开个桶就行。
这里略微解释下为什么 则 与 使用的印章相同,因为“若 正好被印刷必有印章 是其的 ” 由 的定义有 是 的前缀,所以 使用的印章为 , 使用的印章为 ,又因为 ,所以它俩用的印章相同。
具体实现见代码。
复杂度分析
-
时间复杂度:KMP 求 数组 ,DP 也是 ,总共 。
-
空间复杂度:。
#include<bits/stdc++.h> using namespace std; using ll = long long; const int MAXN = 5e5 + 5; string s; int n, nxt[MAXN], dp[MAXN], t[MAXN]; int main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> s; n = s.size(), s = "#" + s; for(int i = 2; i <= n; i++){ int pos = nxt[i - 1]; for(; pos && s[i] != s[pos + 1]; pos = nxt[pos]); nxt[i] = s[i] == s[pos + 1] ? pos + 1 : 0; }//KMP 求 nxt 数组 dp[1] = 1, t[dp[1]] = 1;//注意初始化问题 for(int i = 2; i <= n; i++){ if(t[dp[nxt[i]]] >= i - nxt[i]){//存在符合要求的 j dp[i] = dp[nxt[i]]; }else{ dp[i] = i;//否则只能为 i } t[dp[i]] = i;//只取 dp[i] 相同的 i 的最大值 } cout << dp[n]; return 0; } -
- 1
信息
- ID
- 3190
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者