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

文章目录

题目

「POI2011 R2 Day1」差值 Difference

分析

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

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

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

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

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

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

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

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

时间复杂度 O ( 2 6 2 n ) O(26^2n) O(262n),但实际上会小一些,因为只有上面的情况一会多一个 26 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值