Invertible Bracket Sequences
题目描述
A regular bracket sequence is a bracket sequence that can be transformed into a correct arithmetic expression by inserting characters ‘1’ and ‘+’ between the original characters of the sequence. For example:
- bracket sequences “()()” and “(())” are regular (the resulting expressions are: “(1)+(1)” and “((1+1)+1)”);
- bracket sequences “)(”, “(” and “)” are not.
Let’s define the inverse of the bracket sequence as follows: replace all brackets ‘(’ with ‘)’, and vice versa (all brackets ‘)’ with ‘(’). For example, strings “()((” and “)())” are inverses of each other.
You are given a regular bracket sequence s s s. Calculate the number of pairs of integers ( l , r ) (l,r) (l,r) ( 1 ≤ l ≤ r ≤ ∣ s ∣ 1 \le l \le r \le |s| 1≤l≤r≤∣s∣) such that if you replace the substring of s s s from the l l l-th character to the r r r-th character (inclusive) with its inverse, s s s will still be a regular bracket sequence.
输入描述
The first line contains a single integer t t t ( 1 ≤ t ≤ 1 0 4 1 \le t \le 10^4 1≤t≤104) — the number of test cases.
The only line of each test case contains a non-empty regular bracket sequence; it consists only of characters ‘(’ and/or ‘)’.
Additional constraint on the input: the total length of the regular bracket sequences over all test cases doesn’t exceed 2 ⋅ 1 0 5 2 \cdot 10^5 2⋅105.
输出描述
For each test case, print a single integer — the number of pairs ( l , r ) (l,r) (l,r) meeting the conditions from the statement.
样例 #1
样例输入 #1
4
(())
()
()()()
(()())(())
样例输出 #1
1
0
3
13
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// 用st表封装的区间最值RMQ
// 当前为区间最大值,可修改为区间最小值
template <class T>
struct RMQ
{
int n;
vector<T> a;
vector<array<T, 30>> f;
function<T(T, T)> func;
RMQ(){};
RMQ(vector<T> init_, function<T(T, T)> func_)
{
work(init_, func_);
}
void work(vector<T> &init_)
{
work(init_, [&](int x, int y)
{ return max(x, y); });
}
void work(vector<T> &init_, function<T(T, T)> func_)
{
a = init_;
this->func = func_;
n = a.size();
f.assign(n, {});
for (int i = 1; i < n; i++)
f[i][0] = a[i];
for (int j = 1; j <= __lg(n) + 1; j++)
{
for (int i = 1; i + (1 << j) - 1 < n; i++)
{
f[i][j] = func(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
}
T query(int l, int r)
{
int k = log2(r - l + 1);
return func(f[l][k], f[r - (1 << k) + 1][k]);
}
};
// 使用指南:
// 1.RMQ<int>rmq;
// 2.rmq.work(a);
// 注释:a为数组,大小为n+1,1~n对应题目所给数组下标,a[0]不参与区间最值运算
void solve()
{
string s;
cin >> s;
s = ' ' + s;
int n = s.size();
vector<int> a(n + 1);
for (int i = 1; i <= n; i++)
{
a[i] = (s[i] == '(') ? 1 : -1; // 映射为正负1,方便运算
}
vector<int> p(n + 1, 0); // 前缀和
for (int i = 1; i <= n; i++)
{
p[i] = p[i - 1] + a[i];
}
RMQ<int> rmq;
rmq.work(p); // 处理区间最大值
map<int, vector<int>> mp; // 将前缀和相同的位置放在一起,其中任取(pos1,pos2]区间,左括号和右括号出现的次数相同,这样才有可能对答案产生贡献(至于为什么是可能,是因为要去掉翻转后右括号的数量大于左括号数量的情况)
for (int i = 1; i < n; i++)
{
mp[p[i]].push_back(i);
}
ll ans = 0;
for (auto &[val, tmp] : mp) // C17标准才能使用
{
for (int i = 0; i < tmp.size(); i++)
{
// 双指针
int left = i;
// 二分查找满足条件的右侧指针的最大索引
int l = i, r = tmp.size() - 1;
while (l < r)
{
int mid = (l + r + 1) / 2;
if (2 * val >= rmq.query(tmp[left] + 1, tmp[mid])) // 保证翻转后在每个前缀和位置右括号的数量都不大于左括号数量
l = mid;
else
r = mid - 1;
}
int right = l;
ans += right - left; // 如果(tmp[left],tmp[right]]区间的最大值满足条件,那么(tmp[left],tmp[i]],left<i<=right的所有区间都满足条件,这也是为什么我们能使用二分查找的原因
}
}
cout << ans << '\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}