Luogu7993 solution
Problem
在仅包含字符 H 和 G 的字符串 \(s\) 中,有多少个子串满足:
- 包含一个 H,包含至少两个 G;
- 包含一个 G,包含至少两个 H。
link->https://www.luogu.com.cn/problem/P7993
Solution
第一反应是枚举字符串左端点 \(l\),右端点 \(r\),然后统计在 \([l,r]\),范围内 G 和 H 分别出现的次数。时间复杂度 \(Θ(n^3)\),前缀和优化后 \(Θ(n^2)\),面对 \(n \le 5 \cdot 10^5\) 的数据范围望而生畏。
既然打暴力行不通,那么只能考虑数学解法:
仔细观察满足条件的子串要求,发现了什么?有一个字符只出现一次!
根据这个条件,我们可以设计出合理的算法:枚举满足要求的子串中心点 \(s_i\) (即只出现一次的那个字符的位置)。如果它可以像左拓展至 \(l\)(即 \(s_{l-1}=s_i\)),可以像右拓展至 \(r\)(即 \(s_{r+1}=s_i\)),那 端点在 \([l,r]\) 之间且经过 \(i\) 的子串(长度 \(len \ge 3\))均满足要求!
最坏情况时间复杂度 \(Θ(n^2)\),预处理每个 \(i\) 的 \(l\) 和 \(r\) 后 \(Θ(n)\),完美通过此题。
更多细节在代码中展示。
Code
#include<bits/stdc++.h>
#define int long long //注意这个测试点的答案可能无法用标准的 32 位整数型存储
using namespace std;
const int N=5e5+5;
int pre[N],nxt[N],n,ans;
signed main() {
string s;
cin>>n>>s;
int preH=-1,preG=-1; //左端点初值默认-1
for(int i=0;i<s.size();i++) { //预处理l
if(s[i]=='H') pre[i]=preH,preH=i;
if(s[i]=='G') pre[i]=preG,preG=i;
}
int nxtH=n,nxtG=n; //右端点初值默认n
for(int i=s.size()-1;i>=0;i--) { //预处理r
if(s[i]=='H') nxt[i]=nxtH,nxtH=i;
if(s[i]=='G') nxt[i]=nxtG,nxtG=i;
}
for(int i=0;i<s.size();i++) {
ans+=(i-pre[i])*(nxt[i]-i)-1;
/*根据乘法原理,端点在[l,r]之间且经过i的子串个数为左可拓展长度*右可拓展长度*/
if(nxt[i]!=i+1) --ans; //处理长度小于3的子串
if(pre[i]!=i-1) --ans;
}
printf("%lld",ans);
return 1;
}