BZOJ4964 : 加长的咒语

把$($看作$-1$,$)$看作$1$,设$a$为前缀和,则相当于找两个位置$x,y$使得$a[x]=a[y]$,且$a[x]$是$[x,y]$的区间最大值。

求出询问区间的最大值$o$,然后找到$o$在该区间内最左和最右的出现位置,将其作为答案。

那么剩下的答案只可能在$[l,o)$或$(o,r]$,以$[l,o)$为例。

通过单调栈求出每个位置$i$往右最长延伸长度$g[i]$,使得中间$a[i]$是区间最大值,且$a[i]=a[i+g[i]]$。

那么在$[l,o)$中,因为$o$的阻隔,必然满足$i+g[i]<o$,找到最大的即可。

时间复杂度$O(n\log n)$。

 

#include<cstdio>
#include<algorithm>
using namespace std;
typedef pair<int,int>P;
const int N=400010,M=1050000,inf=~0U>>1;
int n,m,i,x,y,l,r,o,ans,a[N],q[N],t,f[N],g[N],v[M],vf[M],vg[M];P b[N];char s[N];
inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';}
inline void up(int&a,int b){a<b?(a=b):0;}
void build(int x,int a,int b){
  if(a==b){
    v[x]=::a[a];
    vf[x]=f[a];
    vg[x]=g[a];
    return;
  }
  int mid=(a+b)>>1;
  build(x<<1,a,mid),build(x<<1|1,mid+1,b);
  v[x]=max(v[x<<1],v[x<<1|1]);
  vf[x]=max(vf[x<<1],vf[x<<1|1]);
  vg[x]=max(vg[x<<1],vg[x<<1|1]);
}
int ask(int x,int a,int b,int c,int d,int*v){
  if(c<=a&&b<=d)return v[x];
  int mid=(a+b)>>1,t=-inf;
  if(c<=mid)t=ask(x<<1,a,mid,c,d,v);
  if(d>mid)up(t,ask(x<<1|1,mid+1,b,c,d,v));
  return t;
}
int main(){
  scanf("%d%d%s",&n,&m,s+2);n++;
  for(i=2;i<=n;i++)a[i]=a[i-1]+(s[i]==')'?1:-1);
  for(i=1;i<=n;i++)b[i]=P(a[i],i);
  sort(b+1,b+n+1);
  for(q[t=0]=0,i=1;i<=n;q[++t]=i++){
    while(t&&a[i]>=a[q[t]])t--;
    f[i]=i-upper_bound(b+1,b+n+1,P(a[i],q[t]))->second;
  }
  for(q[t=0]=n+1,i=n;i;q[++t]=i--){
    while(t&&a[i]>=a[q[t]])t--;
    g[i]=(lower_bound(b+1,b+n+1,P(a[i],q[t]))-1)->second-i;
  }
  build(1,1,n);
  while(m--){
    read(x),read(y);y++;
    o=ask(1,1,n,x,y,v);
    l=lower_bound(b+1,b+n+1,P(o,x))->second;
    r=(upper_bound(b+1,b+n+1,P(o,y))-1)->second;
    ans=r-l;
    if(l>x)up(ans,ask(1,1,n,x,l-1,vg));
    if(r<y)up(ans,ask(1,1,n,r+1,y,vf));
    printf("%d\n",ans);
  }
  return 0;
}

  

posted @ 2017-08-10 02:19  Claris  阅读(497)  评论(0编辑  收藏  举报