LOJ2161 「POI2011 R2 Day1」差值 Difference(细节DP)

文章目录

题目

「POI2011 R2 Day1」差值 Difference

分析

考虑枚举两个字符分别作为子序列的出现次数最多和最少的字符。一个性质是,这两个字符到底是不是次数最多或最少的字符并不重要,我们只需要统计最大差值,就能自动避免不符合要求的情况(因为不符合要求的情况一定比大差值小)。

简单来说,枚举字符 a a b b 和区间 [ l , r ] [l, r] ,计算 [ l , r ] [l, r] a a b b 的个数 c n t a cnt_a c n t b cnt_b a n s = max { c n t a c n t b } ans = \max\{cnt_a - cnt_b\}
显然这样是 O ( 2 6 2 n 2 ) O(26^2n^2) 的,需要优化。枚举了 a , b a, b 过后,若把所有 a a 视作 1 1 b b 视作 1 -1 、其他字符视作 0 0 ,那么问题变为求最大子段和,记录前面的DP最大值即可, O ( 2 6 2 n ) O(26^2n) 。但是,最大的问题在于要求子段中 a , b a, b 的数量都要大于 0 0 ,所以这种方法比较难以解决。

考虑换一种方式,先枚举 a a ,然后同时对所有 b b 做DP,定义 d p [ b ] [ i ] dp[b][i] 表示 [ 1 , i ] [1, i] 的所有子段中, c n t a c n t b cnt_a - cnt_b 的最大值,为了确保这当中的 a , b a, b 数量均大于 0 0 ,考虑记录两个东西:

  • d p [ b ] [ i ] [ 0 ] dp[b][i][0] [ 1 , i ] [1, i] 的所有子段中, c n t a c n t b cnt_a - cnt_b 的最大值,要求 c n t b 0 cnt_b \neq 0
  • d p [ b ] [ i ] [ 1 ] dp[b][i][1] [ 1 , i ] [1, i] 的所有子段中, c n t a c n t b cnt_a - cnt_b 的最大值, c n t b cnt_b 可以为 0 0

注意 c n t a cnt_a 没有必要做规定,因为如果 c n t a cnt_a 0 0 ,算出来的必定是非正数,而 a n s ans 的初值肯定是 0 0 ,不会更新答案。

然后对当前字符分两种情况转移:

  • s i = a s_i = a ,所有 d p [ c ] [ i ] [ 0 / 1 ] dp[c][i][0/1] 的值 + 1 +1 ,这样肯定更优;
  • s i a s_i \neq a ,那么考虑更新 d p [ s i ] [ i ] [ 0 / 1 ] dp[s_i][i][0/1] 的值:
    • d p [ s i ] [ i ] [ 0 ] = d p [ s i ] [ i 1 ] [ 1 ] 1 dp[s_i][i][0] = dp[s_i][i - 1][1] - 1 ,将 s j s_j 加入最优解,这样还是最优解,因为 d p [ s i ] [ i 1 ] [ 1 ] dp[s_i][i - 1][1] 是所有情况的最优解;
    • d p [ s i ] [ i ] [ 1 ] = max { d p [ s i ] [ i 1 ] [ 1 ] 1 , 0 } dp[s_i][i][1] = \max\{dp[s_i][i - 1][1] - 1, 0\} ,如果小于 0 0 了,就什么都不要了(因为 c n t b cnt_b 可以为 0 0 ),重新计数。

注意一下初值: d p [ c ] [ 0 ] = , d p [ c ] [ 1 ] = 0 dp[c][0] = -\infty, dp[c][1] = 0 。然后时刻更新答案即可。

时间复杂度 O ( 2 6 2 n ) O(26^2n) ,但实际上会小一些,因为只有上面的情况一会多一个 26 26

代码

可以滚动一下数组。

#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;
}

猜你喜欢

转载自blog.csdn.net/C20190102/article/details/105155337
今日推荐