题目
「POI2011 R2 Day1」差值 Difference
分析
考虑枚举两个字符分别作为子序列的出现次数最多和最少的字符。一个性质是,这两个字符到底是不是次数最多或最少的字符并不重要,我们只需要统计最大差值,就能自动避免不符合要求的情况(因为不符合要求的情况一定比大差值小)。
简单来说,枚举字符
,
和区间
,计算
中
和
的个数
和
,
。
显然这样是
的,需要优化。枚举了
过后,若把所有
视作
、
视作
、其他字符视作
,那么问题变为求最大子段和,记录前面的DP最大值即可,
。但是,最大的问题在于要求子段中
的数量都要大于
,所以这种方法比较难以解决。
考虑换一种方式,先枚举 ,然后同时对所有 做DP,定义 表示 的所有子段中, 的最大值,为了确保这当中的 数量均大于 ,考虑记录两个东西:
- : 的所有子段中, 的最大值,要求 ;
- : 的所有子段中, 的最大值, 可以为 。
注意 没有必要做规定,因为如果 为 ,算出来的必定是非正数,而 的初值肯定是 ,不会更新答案。
然后对当前字符分两种情况转移:
- ,所有 的值 ,这样肯定更优;
-
,那么考虑更新
的值:
- ,将 加入最优解,这样还是最优解,因为 是所有情况的最优解;
- ,如果小于 了,就什么都不要了(因为 可以为 ),重新计数。
注意一下初值: 。然后时刻更新答案即可。
时间复杂度 ,但实际上会小一些,因为只有上面的情况一会多一个 。
代码
可以滚动一下数组。
#include <algorithm>
#include <cstdio>
const int MAXN = 1000000;
const int INF = 0x3f3f3f3f;
int N, Dp[30][2]; // 0 必须出现过 / 1 可以没出现过
char S[MAXN + 5];
int main() {
scanf("%d%s", &N, S + 1);
int Ans = 0;
for (int x = 0; x < 26; x++) {
for (int y = 0; y < 26; y++)
Dp[y][0] = -INF, Dp[y][1] = 0;
for (int i = 1; i <= N; i++) {
if (S[i] == x + 'a') {
for (int y = 0; y < 26; y++) {
Dp[y][0]++, Dp[y][1]++;
Ans = std::max(Ans, Dp[y][0]);
}
}
else {
int y = S[i] - 'a';
Dp[y][0] = Dp[y][1] - 1; // 将 y 补到后面
Dp[y][1] = std::max(0, Dp[y][1] - 1); // 可以不要当前的 y, 重新计数
Ans = std::max(Ans, Dp[y][0]);
}
}
}
printf("%d", Ans);
return 0;
}