P1063 康神开播了 官方题解
Gioush OJ · P1063 康神开播了
【题意简述】
每次可以从当前字符串的左端或右端取出一个字符;若当前串形如 ,还可以把 加入答案,同时删去左右两侧的 与 。求最终答案的字典序最小值。
【正解】
维护当前尚未处理的区间 。若 ,所有合法操作接下来输出的第一个字符只能从两端产生,因此直接取较小的一端。
下面考虑 。设左端连续的 长度为 ,右端连续的 长度为 ;越过两段后遇到的字符分别为 。若整个区间都由 构成,最优答案显然是
否则只需比较 与 :
- 若 且 ,分别使用前两类操作取完左右两端字符段,共输出 个 ;
- 若 且 ,输出 个 ,并从两端各删去这么多个 ;
- 其余情况下 分居 两侧。若 ,输出左侧的 个 并删去左段;否则对称地处理右段。
预处理每个位置所在等值段的左右端点,就能在 时间取得 。每次循环至少删去一个字符,所以总复杂度为线性。
【正确性证明】
若两端字符不同,选择较大者会在答案的第一个不同位置立刻变劣,因此取较小端是唯一可能的最优选择。
若两端同为 ,在遇到非 字符前,任意方案产生的前缀都只含 。第三种操作中任意长度的 ,都等价于连续执行 次 为单字符的成对收缩:原操作要求中间的 非空,所以逐字符收缩的每一步都合法;反向把这些收缩合并也显然合法。因此这里只需考虑“单端取出一个 ”和“输出一个 并成对删去两端 ”。方案优劣由输出多少个 后第一个实际输出的非 字符决定。
- 当 时,若使用一次成对收缩,可以把它拆成依次从两端各取一个 ;这样在首次暴露大于 的字符前会多得到一个 ,字典序严格更小。因此最优方案不做成对收缩,而是分别取完两端的 ,共输出 个 ;
- 当 时,首次暴露小于 的字符前至少要输出 个 ,连续成对收缩恰好达到这个下界,所以输出 个 后从两端各删去相同数量;
- 当 分居 两侧时,第一个实际输出的非 字符必须来自较小的一侧。即使较大侧的字符 先成为端点,只要另一端仍为 ,就有 ,最优方案不会输出 ;较小侧的 成为端点后又有 ,所以首个非 输出仍为 。若较小字符来自左侧,至少要消耗左侧的 个 ,而每次操作至多消耗一个左端 ,故至少输出 个 ;全部从左端取出恰好达到下界。期间若成对删去右端 ,只会提前丢掉仍小于 的 ,不会更优。右侧较小时完全对称。
若当前区间全部由 构成,成对收缩能最大程度减少答案长度;但题目要求中间串 非空,所以剩两个字符时不能继续收缩。奇数长度最终留下一个字符,偶数长度最终留下两个字符,答案长度均为 。
四种情况覆盖所有可能。每一步都得到所有合法方案中字典序最小的下一段,删去该段后问题仍与原问题同型;由归纳法,算法得到全局字典序最小答案。
【复杂度】
每个字符至多被预处理和删除各一次,时间复杂度为 ,空间复杂度为 。
【参考代码】
#include <algorithm>#include <cstdio>#include <cstring>#include <string> const int MaxN = 500010; char S[MaxN];int Left[MaxN], Right[MaxN]; int main() { int c, T; scanf("%d%d", &c, &T); while (T--) { scanf("%s", S + 1); int n = (int)strlen(S + 1); Left[1] = 1; for (int i = 2; i <= n; ++i) Left[i] = S[i] == S[i - 1] ? Left[i - 1] : i; Right[n] = n; for (int i = n - 1; i >= 1; --i) Right[i] = S[i] == S[i + 1] ? Right[i + 1] : i; std::string Ans; int l = 1, r = n; while (l <= r) { if (S[l] != S[r]) { if (S[l] < S[r]) Ans.push_back(S[l++]); else Ans.push_back(S[r--]); continue; } char x = S[l]; int p = std::min(Right[l], r); int q = std::max(Left[r], l); if (p >= q) { Ans.append((r - l + 1) / 2 + 1, x); break; } int L = p - l + 1, R = r - q + 1; char a = S[p + 1], b = S[q - 1]; if (a > x && b > x) { Ans.append(L + R, x); l = p + 1; r = q - 1; } else if (a < x && b < x) { int d = std::min(L, R); Ans.append(d, x); l += d; r -= d; } else if (a < b) { Ans.append(L, x); l = p + 1; } else { Ans.append(R, x); r = q - 1; } } printf("%s\n", Ans.c_str()); } return 0;}