[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;
}
夜畔流离回,暗叹永无殿。
独隐万花翠,空寂亦难迁。
千秋孰能为,明灭常久见。
但得心未碎,踏遍九重天。