[CF653F] Paper Task

\(\text{Problem}:\)Paper Task

\(\text{Solution}:\)

合法括号串的性质:左括号个数等于右括号个数;任意一段后缀中,左括号个数小于等于右括号。

( 看作 \(-1\),) 看作 \(1\),那么区间 \([l,r]\) 对应的子串是合法括号串的条件:\(1.\) 区间和为 \(0\);\(2.\) 区间内任意一段后缀和都大于等于 \(0\)。

计算本质不同子串,可以想到 \(\text{SAM}\)。对于状态 \(x\),我们从它对应的 \(\text{endpos}\) 等价类中随便选一个算出答案,然后乘上 \(size_{x}\) 即可。现在问题转化为:求出右端点 \(i\),左端点在区间 \([l,r]\) 内的子串有多少是合法的。

发现当右端点 \(i\) 固定时,左端点越小,越可能不满足条件 \(2\),所以可以二分出满足条件 \(2\) 最小的左端点 \(p\)。区间和为 \(0\) 可以看作前缀和相等,故直接查询区间内有多少数等于 \(x\) 即可。

具体实现:设序列前缀和为 \(a_{i}\),用 \(\text{ST}\) 表维护区间 \(a_{i}\) 最小值得到 \(p\);值域很小,可以 \(vector\) 上二分查找区间有多少数等于 \(x\)。时间复杂度 \(O(n\log n)\)。

\(\text{Code}:\)

#include <bits/stdc++.h>
#pragma GCC optimize(3)
//#define int long long
#define ri register
#define mk make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define is insert
#define es erase
#define vi vector<int>
#define vpi vector<pair<int,int>>
using namespace std; const int N=1000010, M=21;
inline int read()
{
	int s=0, w=1; ri char ch=getchar();
	while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
	while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch^48), ch=getchar();
	return s*w;
}
char s[N];
int n,qz[N],st[N][M],lg[N]; long long Ans;
vector<int> g[N]; int h[N];
struct SAM
{
	int link[N],len[N],ch[N][2],fir[N];
	int tot,lst;
	inline SAM() { tot=lst=1; link[1]=len[1]=0; memset(ch[1],0,sizeof(ch[1])); }
	inline void Extend(int c)
	{
		int now=++tot;
		int p=lst;
		while(p && !ch[p][c]) ch[p][c]=now, p=link[p];
		len[now]=len[lst]+1;
		fir[now]=len[now];
		if(!p) { lst=now, link[now]=1; return; }
		int q=ch[p][c];
		if(len[q]==len[p]+1) link[now]=q;
		else
		{
			int nq=++tot;
			fir[nq]=fir[q];
			len[nq]=len[p]+1;
			memcpy(ch[nq],ch[q],sizeof(ch[q]));
			link[nq]=link[q];
			link[q]=link[now]=nq;
			while(p && ch[p][c]==q) ch[p][c]=nq, p=link[p];
		}
		lst=now;
	}
	inline void Renew()
	{
		for(ri int i=1;i<=tot;i++) link[i]=len[i]=0, memset(ch[i],0,sizeof(ch[i]));
		tot=lst=1;
	}
}A;
inline int Ask(int x,int y)
{
	int k=lg[y-x+1];
	return max(st[x][k],st[y-(1<<k)+1][k]);
}
inline int Find(int l,int r,int p)
{
	int L=l, R=r, tt=-1;
	while(L<=R)
	{
		int mid=(L+R)/2;
		if(qz[p]-Ask(mid-1,p-1)>=0) tt=mid, R=mid-1;
		else L=mid+1;
	}
	return tt;
}
inline int Get(int l,int x)
{
	if(l<0) return 0;
	int L=0, R=h[x]-1, res=0;
	while(L<=R)
	{
		int mid=(L+R)/2;
		if(g[qz[x]+n][mid]<=l) res=mid+1, L=mid+1;
		else R=mid-1;
	}
	return res;
}
signed main()
{
	lg[0]=-1;
	for(ri int i=1;i<N;i++) lg[i]=lg[i>>1]+1;
	n=read();
	scanf("%s",s+1);
	g[n].eb(0);
	for(ri int i=1;i<=n;i++)
	{
		qz[i]=qz[i-1];
		if(s[i]=='(') A.Extend(0), qz[i]--;
		else A.Extend(1), qz[i]++;
		h[i]=(int)g[qz[i]+n].size();
		g[qz[i]+n].eb(i);
	}
	for(ri int i=1;i<=n;i++) st[i][0]=qz[i];
	for(ri int i=1;(1<<i)<=n;i++)
	for(ri int j=0;j+(1<<i)-1<=n;j++)
	st[j][i]=max(st[j][i-1],st[j+(1<<(i-1))][i-1]);
	for(ri int i=2;i<=A.tot;i++)
	{
		int t=A.fir[i];
		int l=t-A.len[i]+1;
		int r=t-A.len[A.link[i]];
		int pos=Find(l,r,t);
		if(pos==-1) continue;
		Ans+=Get(r-1,t)-Get(pos-2,t);
	}
	printf("%lld\n",Ans);
	return 0;
}
上一篇:原创 | 算法工程师为什么成天做数据,都做哪些数据? **


下一篇:Oracle入门学习实例讲解——6.Oracle开发和应用4