题意:
找出一串括号序列中的最长的合法子串的长度以及数目。
思路:
如果对于括号序列,'('表示+1,')'表示-1,一串合法的括号序列要保证这样的前缀和序列首尾都是0,且序列中间的前缀和值不能小于0。对于这道题来说,找到每个前缀和sum[p]之后与之相邻最远的且相同的前缀和的位置q,那么[p+1,q]这一段就是以这个p+1开头的最长的合法括号序列。这里可以对于每个位置p利用单调栈求出第一个比当前位置的数小的位置,在这之间的相同前缀和肯定不会比sum[p]小,一开始要将所有相同的sum[i]的位置保存在vector里,然后对于单调栈求出的范围r[i]之内进行二分,找到最远的合法位置,并且更新最大值和数目即可。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <stack>
#include <vector>
using namespace std;
const int MAXN = 1111111;
const int mid = 1000000;
char str[MAXN];
int sum[MAXN], cnt[MAXN], r[MAXN];
vector <int> pos[MAXN * 2];
int main() {
scanf("%s", str + 1);
int n = strlen(str + 1);
sum[0] = 0;
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + (str[i] == '(' ? 1 : -1);
pos[mid + sum[i]].push_back(i);
}
stack <int> sta;
int maxlen = 0;
for (int i = n; i >= 0; i--) {
while (!sta.empty() && sum[sta.top()] >= sum[i]) sta.pop();
if (sta.empty()) r[i] = n + 1;
else r[i] = sta.top();
sta.push(i);
}
for (int i = 0; i <= n; i++) {
int tmp = mid + sum[i];
int p = lower_bound(pos[tmp].begin(), pos[tmp].end(), r[i]) - pos[tmp].begin();
if (p > 0) {
maxlen = max(maxlen, pos[tmp][p - 1] - i);
cnt[pos[tmp][p - 1] - i]++;
}
}
if (maxlen == 0) puts("0 1");
else printf("%d %d\n", maxlen, cnt[maxlen]);
return 0;
}