括号序列
【问题᧿述】
给定一个括号求正确使用括号的字串。
【输入文件】
输入文件包括一行一个字符串,为给定的括号序列。
【输出文件】
输出一个整数,为标准的括号序列的子串的个数。
【输入输出样例】
()(())
4
【数据规模和约定】
设输入字符串的长度为n。
对于30%的数据,满足
n
≤
200
n ≤ 200
n≤200。
对于60%的数据,满足
n
≤
5000
n≤ 5000
n≤5000。
对于100%的数据,满足
1
≤
n
≤
1
0
6
1 ≤ n≤ 10^6
1≤n≤106。
正解真的是好优秀,当时只打了暴力,加了一点剪枝,60%的数据还过了几个,
然后又去问了一个80分做法???(也是卡过数据的)
显然30分左右暴力直接枚举两个端点判断是否是一个合法括号序列,dfs判读端点内是否合法,对于一个串,如果起点或者终点就不像是合法(比如长这样 ) . . . ) ) ... ) )...))的我们就直接跳过,对于不能跳过的,我们就开一个计数器,遇到 “ ( ” “(” “(”就加一反之减一,如果他合法那么在终点时和一定为零,并且在中间的时候不会出现负数的情况因为出现负数说明括号是反着的,像这样 ( ) ) ( ( ) ())(() ())(()这样在结尾的时候和是零但是中间会出现负数,然后我们暴力判断变负数就退出,跑到枚举的右端点为0就答案加一即可。
80分
这个大佬应该会写,毕竟是她教的
(太累了,先鸽着,有空再补)
100分
如果你看了我的暴力解法的话,可能会觉得有好多好多优化的方法,但是我中间开,同时到第一题时有点不想写,满脑子都是栈的写法我不会写,就随便写了个暴力敷衍了一下,虽然自己感觉可以优化但是没有写。
那么我们现在扫一遍字符串,同样是遇到"(“加一遇到”)"减一,那么我们在知道暴力判断合法的情况下,考虑怎么把这个判断从n2的左右端点中移除出来,(我大约n3的做法居然过了35分,对于30%的数据n≤200);
显然我们枚举再判断的时候有大量的区间被重复扫描了,那么我们考虑什么时候合法,
在暴力中显然是从左到右扫到零的时候,那么在一次性扫的时候就是前后在扫过两个点的时候他们的计数器的值都是相等的时候,那么内部合法就是在从左到右的时候左右两端时的数值是最小的
如果我们只考虑计数器值相等这一个条件,我们开一个长度为
2
n
2n
2n的数组,这里我用的是sum[2n],从
i
=
1
i = 1
i=1到
n
n
n 开始扫,记录计数器num每个值出现的次数每次答案加上之前出现的次数就行,就是
a
n
s
+
=
s
u
m
[
n
u
m
+
n
]
,
+
+
s
u
m
[
n
u
m
+
n
]
ans+= sum[num + n],++sum[num + n]
ans+=sum[num+n],++sum[num+n]sum + n是为了防止下标越界,防止开始时num减成负数,数组没法存
那么我们想想怎么判断合法,或者说滤除不合法的贡献,因为你可能对前一个有贡献,中间不合法,对后面实际上是不能有贡献的。
讲到这里,保证合法的方法就出来了,对于一个num[i]如果存在num[i + k] = num[i],那么以i+k的合法序列左端点不会小于i,,那么如果前面仍然出现了num[i - t] = num[i]就会出现多余的贡献,所以我们每次统计答案的时候就在统计完的时候把之前统计的个数清零。而能出现num[i] = num[i + k]的时候只能是在num值向下减的时候,所以在s[i] == ')'的时候操作就行了
#include<bits/stdc++.h>
#define MAXN 5000010
#define ll long long
using namespace std;
char s[MAXN];
int sum[MAXN];
int main()
{
scanf("%s", s);
int n = strlen(s);
ll ans = 0; int num = 0;
sum[0 + n] = 1;
for(int i = 0; i < n; ++i){
if(s[i] == '('){
++num;
++sum[num + n];
}
else{
sum[num + n] = 0;
--num;
ans += (ll)sum[num + n];
++sum[num + n];
}
}
printf("%lld\n", ans);
return 0;
}