题面
英文题面
题意:给定一个长度为\(n\)的括号串,问有多少种不同的合法括号子串。
\(n \leq 5\times 10^5\)
题解:
先考虑暴力的做法:枚举左端点,然后每次扫一遍,有括号平衡就将其加入答案,再用哈希+map判一下重。
再考虑不需要去重时的快速做法。
令\(s_i\)表示前\(i\)个字符,左括号个数与右括号个数的差。发现当固定左端点\(l\)时,合法的右端点\(r\)需要满足:
1.\(s_l = s_r\)
2.\(\forall_{i=l}^{r} , s_i \geq s_l\)
所以我们开一个树状数组记一下权值,枚举左端点,二分出满足第二个限制的最大的\(r\),在树状数组上查询即可。
二分的check可以用rmq做到O(1)查询。
那么如何去重呢?考虑后缀数组的height数组的意义,不难发现对于每个\(l\),答案只需要减去\(r\)在\([l,l+height_{rank_l}-1]\)这段的贡献即可。
时间复杂度:\(O(nlogn)\)
代码:
#include
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
typedef long long ll;
#define I inline void
#define IN inline int
char c[505000];
int n,m,buc[505000],sa[505000],tot,rk[505000],tp[505000],hei[505000];
int s[505000],f[505000][22],lg[505000];
vectorst[1010000];
ll ans;
I build_sa(){
m=2;
F(i,1,n)buc[rk[i]=(c[i]=='('?1:2)]++;
F(i,1,m)buc[i]+=buc[i-1];
FOR(i,n,1)sa[buc[rk[i]]--]=i;
for(re k=1;k<=n;k<<=1){
tot=0;
F(i,n-k+1,n)tp[++tot]=i;
F(i,1,n)if(sa[i]>k)tp[++tot]=sa[i]-k;
F(i,1,m)buc[i]=0;
F(i,1,n)buc[rk[i]]++;
F(i,1,m)buc[i]+=buc[i-1];
FOR(i,n,1)sa[buc[rk[tp[i]]]--]=tp[i],tp[i]=0;
swap(rk,tp);
rk[sa[1]]=tot=1;
F(i,2,n)rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?tot:++tot;
if(tot==n)break;
m=tot;
}
re k=0;
F(i,1,n){
if(rk[i]==1)continue;
if(k)k--;
re p=sa[rk[i]-1];
while(c[i+k]==c[p+k])k++;
hei[rk[i]]=k;
}
// F(i,1,n)cout<>1]+1,f[i][0]=s[i];
F(j,1,lg[n])F(i,1,n-(1<=r||c[l]==')')return 0;
re x=l,y=r,mid,w=s[l-1],res;
while(x!=y){
mid=(x+y+1)>>1;
if(ques_min(l,mid)>=w)x=mid;
else y=mid-1;
// cout<>n>>c+1;
build_sa();build_rmq();
// while(1){
// int l,r;cin>>l>>r;cout<