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

posted @ 2022-08-02 08:48  lsj2009  阅读(111)  评论(0)    收藏  举报