1 条题解
-
0
题解
题目类型:双指针 / 线性扫描
核心目标:给定长度为n
的字符串s
,把字符分成两类——'j'
与 非'j'
。在所有子串中,求满足 [ #(\text{非}~'j') \ \ge\ #('j') ] 的最大长度。直观理解:把每个
'j'
视为+1
,把每个“非'j'
”视为-1
。题目要找的是权值和 ≤ 0 的最长子串。
一、思路概述
- 我们按 左端点
l
从左到右 枚举(但会跳跃推进),对每个l
尝试尽可能向右扩展右端点i
。 - 在扩展的过程中维护两组计数:
s1 / s2
:从当前l
开始到当前位置的全局计数(s1
统计'j'
数量,s2
统计非'j'
的数量)。h1 / h2
:从上一次被“清零点”之后到当前位置的局部计数。
- 当全局计数出现
s1 > s2
(即子串权值和 > 0)时,说明再扩大只会更差,立刻停止本次扩展。 - 当局部计数满足
h2 >= h1
时,说明从“清零点”到当前位置的这段已经**“不亏”(非'j'
数量不小于'j'
),于是把“清零点”更新到当前位置(h1 = h2 = 0
),并记录一个可作为结尾的最优位置**wi = i
。
最后本轮确定的最优右端点为:
- 若有记录到
wi
,就用wi
; - 否则用因为
s1 > s2
而停下来的当前位置i
(此时i
恰好是第一个使得不满足的点,最佳结尾在i-1
,但代码里通过wi
的维护实现了等价选择)。
记录本轮答案
ans = max(ans, i - l + 1)
,并把下一轮的l
跳到max(i+1, l+1)
,继续做下一次扩展。另外一个小优化:若
s[l] == 'j'
,直接把l
往右推到**第一个非'j'
**的位置再开始,这能规避明显不优的起点。
二、与代码的对应关系
for (int i=0, l=1, r=n; l<=r; l=max(i+1, l+1)) { while (s[l]=='j' && l<=r) l++; // 1) 起点规避:跳过起始为 'j' 的位置 wi = 0; if (l <= r) { for (i=l; i<=r; i++) { // 2) 右端点从 l 开始扩展 if (s[i]=='j') s1++, h1++; else s2++, h2++; if (s1 > s2) break; // 3) 全局超过:停止扩展(子串和>0) if (h2 >= h1) { // 4) 局部不亏:重置局部计数并记录“最佳结尾” h1 = h2 = 0; wi = i; } } } if (wi) i = wi; // 5) 若有最佳结尾,用它作为本轮右端点 ans = max(ans, 1LL*(i - l + 1)); // 6) 更新答案(最长长度) s1 = s2 = h1 = h2 = 0; // 7) 清空计数,准备下一轮 }
s1/s2
:当前以l
为起点的全局计数;h1/h2
:自上次“清零点”后的局部计数,用来贪心地更新“可作为结尾的最右位置wi
”;wi
:这次扩展得到的“最优右端点”(保证当前子串满足 #非'j'
≥ #'j'
);ans
:全局最大长度。
三、正确性解释(为什么可行)
把
'j'
看作+1
,非'j'
看作-1
。我们要最长的区间和 ≤ 0 的子串。- 若在某个扩展点出现
s1 > s2
(区间和 > 0),再继续扩展只会让子串更大、和更难 ≤ 0,因此立即停止。 - 只要某一段的局部和 ≤ 0(即
h2 >= h1
),就可以把“清零点”移动到该处,把这段“吃掉”。这保证了我们总能把“坏的正贡献”砍掉,保留“最不亏”的结尾,使从l
到当前i
的整体更容易满足 ≤ 0。
于是wi
始终指向最右的、可作为合法结尾的位置。
每轮结束后,从
l
到i
获得了当前起点下的最优合法区间,于是把下一个l
跳到i+1
,避免重复工作。
四、边界与细节
- 输入为
cin >> n >> s+1
:字符串从下标1
开始存放,方便与循环匹配。 - 若整个字符串没有任何非
'j'
,答案自然为0
。 - 多次清零不会漏解:
wi
总能保留“最右合法结尾”,与“首次越界即止”的策略配合可覆盖全部情况。
五、复杂度
- 每个位置作为
l
最多被访问常数次,i
单调不回退,整体为 线性复杂度O(n)
。 - 空间复杂度
O(1)
(除输入字符串外)。
六、代码(与题给一致)
#include<bits/stdc++.h> using namespace std; const long long N=1e6+5; long long n,s1,s2,h1,h2,wi,ans; char s[N]; int main(){ cin>>n>>s+1; for(int i=0,l=1,r=n;l<=r;l=max(i+1,l+1)){ while(s[l]=='j'&&l<=r) l++; wi=0; if(l<=r){ for(i=l;i<=r;i++){ if(s[i]=='j') s1++,h1++; else s2++,h2++; if(s1>s2) break; if(h2>=h1) h1=h2=0,wi=i; } } if(wi) i=wi; ans=max((i-l+1)*1ll,ans); s1=s2=h1=h2=0; } cout<<ans<<'\n'; }
- 我们按 左端点
- 1
信息
- ID
- 3556
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 4
- 已通过
- 0
- 上传者