题目大意:对于一个括号序列,有两种操作。set操作:将区间内的数置同;reverse:将区间内的数取反。对于每个询问,判断该区间的括号是否匹配。
思路:数据规模100000,要在O(logn)或者O(1)内完成操作,线段树维护信息和查询。
对于每个询问,需要O(1)的判断括号是否匹配:将‘(’看做-1,‘)’看做1,从左到右扫一遍,记录连续和最大值,和整个区间的和,只要 区间和==0 && 连续和最大值<=0,则括号匹配。
但是有一个问题:当reverse的操作做完之后,无法在O(1)的时间内更新区间连续和的最大值,因为该最大值恰巧是未操作之前的区间连续和最小值。所以线段树还要维护这个信息。
看看线段树维护的信息:区间和sum,区间连续和最大值mx,区间连续和最小值mi,reverse操作的懒惰标记,set操作的懒惰标记
PS:对于两个懒惰标记的处理,可能是个人原因,我搜到的程序自己看的不大懂,所以自己写了一个很一般的程序。或许是数据比较弱,感觉自己写的可能会有BUG,希望如果读到有错误的地方,予以指正。
对于更新set操作,当他遇到reverse的懒惰标记的时候,可以完全将该标记覆盖,因为无论前面的操作对数列有什么影响,set操作把它们全变成同一个数。对于更新reverse操作,当他遇到set懒惰标记时,实际上的功用就是将set懒惰标记的值变为相反数(想想为什么),比如set标记的值为1,就是将该区间全变为1,想在有个revers操作,不就是将该区间全变为-1么,将1改为-1就ok。还有就是,当他遇到reverse懒惰标记的时候,取个异或值,因为对一个区间做两次reverse操作,相当于什么也没做。
这样做的时候,其实不会出现在同一个区间,reverse懒惰标记和set懒惰标记同时出现。也就是说,两者只会出现一个。(这一块儿就是我看别人的代码看不懂的地方,别人都是两个一块儿考虑的)
更新操作:父亲->sum=左儿子->sum + 右儿子->sum;
父亲->mx=max ( 左儿子->mx,左儿子->sum + 右儿子->mx);
父亲->mi=min ( 左儿子->mi,左儿子->sum + 右儿子->mi);
reverse操作: mx=-mi; mi=-mx; sum=-sum;
#include <iostream> #include<stdio.h> #include<string.h> #include<cmath> using namespace std; const int maxn=100000+10; struct Node { int l,r; int sum; int mx,mi; int lz_r,lz;//lz_r是reverse操作的懒惰标记,lz是set操作的懒惰标记 }node[maxn << 2]; char s[maxn]; int v[maxn]; void push_up(int p,int ls,int rs) { node[p].sum=node[ls].sum+node[rs].sum; node[p].mx=max(node[ls].mx,node[rs].mx+node[ls].sum); node[p].mi=min(node[ls].mi,node[rs].mi+node[ls].sum); } void push_down(int p,int ls,int rs)//结点,左儿子,右儿子 { if(node[p].lz!=0)//如果该区间有set的懒惰标记 { node[ls].sum=(node[ls].r-node[ls].l+1)*node[p].lz; node[ls].mi=min(node[p].lz,node[ls].sum); node[ls].mx=max(node[p].lz,node[ls].sum); node[ls].lz=node[p].lz; if(node[ls].lz_r!=0)node[ls].lz_r=0;//lz向左右传递的时候顺便覆盖之前reverse懒惰标记的值 node[rs].sum=(node[rs].r-node[rs].l+1)*node[p].lz; node[rs].mi=min(node[p].lz,node[rs].sum); node[rs].mx=max(node[p].lz,node[rs].sum); node[rs].lz=node[p].lz; if(node[rs].lz_r!=0)node[rs].lz_r=0; node[p].lz=0;//释放该节点的标记值 } else if(node[p].lz_r!=0)//如果有reverse懒惰标记 { node[ls].sum=-node[ls].sum; int temp=node[ls].mi; node[ls].mi=-node[ls].mx; node[ls].mx=-temp; if(node[ls].lz!=0)//将set懒惰标记置反,同时reverse标记清零 { node[ls].lz=-node[ls].lz; node[ls].lz_r=0; } else node[ls].lz_r^=1;//如果遇到reverse标记,取异或值 node[rs].sum=-node[rs].sum; temp=node[rs].mi; node[rs].mi=-node[rs].mx; node[rs].mx=-temp; if(node[rs].lz!=0) { node[rs].lz=-node[rs].lz; node[rs].lz_r=0; } else node[rs].lz_r^=1; node[p].lz_r=0; } } void make_tree(int p,int a,int b)//建树区间1~n,所以操作的时候区间端点加1 { node[p].l=a;node[p].r=b; node[p].lz=node[p].lz_r=0; if(a==b) { node[p].sum=node[p].mi=node[p].mx=v[a]; return; } int ls=p << 1,rs=ls+1; int mid=(node[p].l+node[p].r) >> 1; make_tree(ls,a,mid); make_tree(rs,mid+1,b); push_up(p,ls,rs); } void modify_set(int p,int a,int b,int x)//set操作 { if(node[p].l==a && node[p].r==b)//如果找到该区间 { node[p].sum=(b-a+1)*x; node[p].mi=min(x,node[p].sum); node[p].mx=max(x,node[p].sum); node[p].lz=x; if(node[p].lz_r!=0)node[p].lz_r=0;//将reverse的标记覆盖 return; } int ls=p << 1,rs=ls+1; int mid=(node[p].l+node[p].r) >> 1; push_down(p,ls,rs); //向下释放懒惰标记 if(b<=mid)modify_set(ls,a,b,x); else if(a>=mid+1)modify_set(rs,a,b,x); else { modify_set(ls,a,mid,x); modify_set(rs,mid+1,b,x); } push_up(p,ls,rs); } void modify_rev(int p,int a,int b)//reverse操作 { if(node[p].l==a && node[p].r==b) { node[p].sum=-node[p].sum; int temp=node[p].mi; node[p].mi=-node[p].mx; node[p].mx=-temp; if(node[p].lz!=0)//改变set标记 { node[p].lz=-node[p].lz; node[p].lz_r=0; } else//或者与之前的reverse操作取异或值 { node[p].lz_r^=1; } return; } int ls=p << 1,rs=ls+1; int mid=(node[p].l+node[p].r) >> 1; push_down(p,ls,rs); if(b<=mid)modify_rev(ls,a,b); else if(a>=mid+1)modify_rev(rs,a,b); else { modify_rev(ls,a,mid); modify_rev(rs,mid+1,b); } push_up(p,ls,rs); } int query_sum(int p,int a,int b) { if(node[p].l==a && node[p].r==b) { return node[p].sum; } int ls=p << 1,rs=ls+1; int mid=(node[p].l+node[p].r) >> 1; push_down(p,ls,rs); if(b<=mid)return query_sum(ls,a,b); else if(a>=mid+1)return query_sum(rs,a,b); else return query_sum(ls,a,mid)+query_sum(rs,mid+1,b); } int query_max(int p,int a,int b) { if(node[p].l==a && node[p].r==b) { return node[p].mx; } int ls=p << 1,rs=ls+1; int mid=(node[p].l+node[p].r) >> 1; push_down(p,ls,rs); if(b<=mid)return query_max(ls,a,b); else if(a>=mid+1)return query_max(rs,a,b); else return max(query_max(ls,a,mid),query_sum(ls,a,mid)+query_max(rs,mid+1,b)); //一定是左区间的和+右区间的max值,wa了2次 } void convert(char *s,int n)//转换成数组 { for(int i=0;i<n;i++) { if(s[i]=='(')v[i+1]=-1; else v[i+1]=1; } } int main() { int t,n,m,a,b; char ss[10],ch[2]; //freopen("in","r",stdin); scanf("%d",&t); for(int kk=1;kk<=t;kk++) { printf("Case %d:\n",kk); scanf("%d",&n); scanf("%s",s); convert(s,n); make_tree(1,1,n); scanf("%d",&m); while(m--) { scanf("%s",ss); if(ss[0]=='s') { scanf("%d %d %s",&a,&b,ch); int ans=ch[0]=='('?-1:1; modify_set(1,a+1,b+1,ans); } else if(ss[0]=='r') { scanf("%d %d",&a,&b); modify_rev(1,a+1,b+1); } else if(ss[0]=='q') { scanf("%d %d",&a,&b);a++;b++; int ans_sum=query_sum(1,a,b); int ans_max=query_max(1,a,b); if(ans_sum==0 && ans_max<=0)printf("YES\n"); else printf("NO\n"); } } printf("\n"); } return 0; }