zoukankan      html  css  js  c++  java
  • CF380C Sereja and Brackets 题解

    题目大意

    维护一段长度 (|s|leq 10^6) 的括号串,需要回答 (mleq 10^5) 次询问,每次询问给定区间 ([l,r]),求 ([l,r])子序列中最长的合法括号串长度。

    分析

    考虑一个括号串的子序列中最长的合法括号串长度,不妨分别计算其中没有匹配的左括号和右括号个数。

    不妨分治计算其中左半子串与右半子串中没有匹配的左括号个数和右括号个数。

    显然左半子串中没有匹配的左括号可以和右半子串中没有匹配的右括号进行匹配。进行合并时减掉即可。

    由于是对于一个括号串的子串进行询问,自然而然地可以想到 Segment Tree。两个区间合并的过程实际上即 pushup 的过程。

    如果说的不够清楚,可以用式子表示一下。下标 (l) 表示未匹配的左括号,(r) 表示未匹配的右括号。

    [egin{cases} ext{tot}_l= ext{left son}_l+ ext{right son}_l-min( ext{left son}_l, ext{right son}_r)\ ext{tot}_r= ext{left son}_r+ ext{right son}_r-min( ext{left son}_l, ext{right son}_r)end{cases} ]

    代码

    #include<bits/stdc++.h>
    #define HohleFeuerwerke using namespace std
    #pragma GCC optimize(3,"Ofast","-funroll-loops","-fdelete-null-pointer-checks")
    #pragma GCC target("ssse3","sse3","sse2","sse","avx2","avx")
    #define int long long
    HohleFeuerwerke;
    inline int read(){
    	int s=0,f=1;char c=getchar();
    	for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    	for(;isdigit(c);c=getchar()) s=s*10+c-'0';
    	return s*f;
    }
    inline void write(int x){
    	if(x<0) putchar('-'),x=-x;
    	if(x>=10) write(x/10);
    	putchar('0'+x%10);
    }
    const int MAXN=1e6+5;
    int n,T;
    char str[MAXN];
    struct node{
    	int l,r;
    	int lsum,rsum;
    }t[MAXN*4];
    inline void update(int i){
    	t[i].lsum=t[i*2].lsum+t[i*2+1].lsum-min(t[i*2].lsum,t[i*2+1].rsum);
    	t[i].rsum=t[i*2].rsum+t[i*2+1].rsum-min(t[i*2].lsum,t[i*2+1].rsum);
    	//由于左右区间合并时不能保证每一个左半区间没有匹配的左括号都对应右边没有匹配的右括号,做减法时取的是较小值。
    }
    inline void build(int i,int l,int r){
    	t[i].l=l,t[i].r=r;
    	if(l==r){
    		if(str[l]=='(') t[i].lsum=1;
    		if(str[l]==')') t[i].rsum=1;
    		return;
    	}
    	int mid=(l+r)/2;
    	build(i*2,l,mid); build(i*2+1,mid+1,r);
    	update(i);
    }
    inline node query(int i,int l,int r){
    	if(t[i].l>=l&&t[i].r<=r){
    		return t[i];
    	}
    	int mid=(t[i].l+t[i].r)/2;
    	if(l>mid) return query(i*2+1,l,r);//待查区间完全在 mid 右侧
    	if(r<=mid) return query(i*2,l,r);//待查区间完全在 mid 左侧
    	//注意这里和普通线段树只需要有交集就查的区别
    	//如果待查区间在两边内都有
    	node tl=query(i*2,l,r),tr=query(i*2+1,l,r);
    	node ans;
    	ans.lsum=tl.lsum+tr.lsum-min(tl.lsum,tr.rsum);
    	ans.rsum=tl.rsum+tr.rsum-min(tl.lsum,tr.rsum);
    	//实际上即合并左右子区间
    	return ans;
    }
    signed main()
    {
    	scanf("%s",str+1);
    	n=strlen(str+1);
    	build(1,1,n);
    	T=read();
    	while(T--){
    		int l=read(),r=read();
    		node tmp=query(1,l,r);
    		write((r-l+1)-tmp.lsum-tmp.rsum);
    		//所求的是匹配的子序列长度。可以用总长-没有匹配的左括号个数-没有匹配的右括号个数来计算 
    		puts("");
    	}
    	return 0;
    }
    
  • 相关阅读:
    .NET 客户端上传本地excel文件到服务器上,并在客户端显示
    C#实现数字字符串左补齐0的3种方法
    C# 输出pdf文件流在页面上显示
    ASP.NET 将数据生成PDF (二)
    asp.net生成PDF文件 (1)
    stl常用的查找算法
    stl中的transform()注意其与for_each的不同点(有无返回值)
    stl中的for_each() 函数的注意事项
    如何在VMware系统中的ubuntu16.04中建立与win7系统的共享文件夹
    关于linux中用vi新建立一个.c文件无法保存,显示E212错误的时候
  • 原文地址:https://www.cnblogs.com/AllWeKnow/p/15367413.html
Copyright © 2011-2022 走看看