牛客国庆训练,CCPC Camp DAY1 J 倍增,括号匹配

https://www.nowcoder.com/acm/contest/201#question

题意:中文不翻译了

解法的个人理解:

  对于一个合法的区间$[L,R]$

  1.显然其左括号的匹配位置都小于等于$R$,其右括号的匹配位置都大于等于$L$,

  2.左括号和右括号数量相同

  3.区间的长度为偶数

  后面两点是必要的,但是不够充分

  第一点是最关键的,我们可以考虑转化

  如果将整个序列左括号所匹配的位置记录下来作为数列$a_i$,同理右括号记为$b_i$原本的询问就等于是询问一个区间最大值和区间最小值了

  即$a_i$的最大值小于$R$,$b_i$的最小值小于$L$,至于具体怎么查,用st表,线段树,树状数组都行

  而实际上,在满足2,3条件之后,最大值和最小值只用计算其中一个即可

  例如满足了左括号的匹配位置都小于$R$,那么区间内的左括号都已经找到归属了,换句话说只要左括号和右括号数目相等,则必然合法

代码如下:

#include <bits/stdc++.h>
#define rep(ii,a,b) for(register int ii=a;ii<=b;++ii)
#define per(ii,a,b) for(register int ii=b;ii>=a;--ii)
using namespace std;
const int maxn=2e6+10,maxm=2e6+10;
int casn,n,m,k,q,a[maxn],stk[maxn];
int l[maxn],sum[maxn],st[maxn][22],top,ll,rr;
inline int rmax(int ll,int rr){
  int len=0;
  while(ll+(1<<len)-1<=rr) len++;
  len--;
  return max(st[ll][len],st[rr-(1<<len)+1][len]);
}
int main() {
	scanf("%d%d%d",&n,&m,&q);
	rep(i,1,n) scanf("%d",&a[i]);
	rep(i,1,n)
		if(a[i]%2) sum[i]=sum[i-1]-1;
		else sum[i]=sum[i-1]+1;
	per(i,1,n)
		if(a[i]%2)stk[++top]=i;
		else{
			if(top&&a[i]/2==a[stk[top]]/2){
        l[i]=stk[top--];
			}else {
        l[i]=maxn+10;
        top=0;
			}
		}
	rep(i,1,n) st[i][0]=l[i];
	rep(i,1,21) rep(j,1,n)
      st[j][i]=max(st[j][i-1],st[min(j+(1<<(i-1)),n)][i-1]);
  while(q--){
    scanf("%d%d",&ll,&rr);
    if((rr-ll+1)%2==0&&sum[rr]==sum[ll-1]&&rmax(ll,rr)<=rr)
      puts("Yes");
    else
      puts("No");
   }
  return 0;
}

  

原文地址:https://www.cnblogs.com/nervendnig/p/9736184.html