D. Invertible Bracket Sequences

D. 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. Calculate the number of pairs of integers (l,r) (1lr|s|) such that if you replace the substring of s from the l-th character to the r-th character (inclusive) with its inverse, s will still be a regular bracket sequence.

Input

The first line contains a single integer t (1t104) — 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 2105.

Output

For each test case, print a single integer — the number of pairs (l,r) meeting the conditions from the statement.

Example

input

4
(())
()
()()()
(()())(())

output

1
0
3
13

Note

In the first example, there is only one pair:

  • (2,3): (()) ()().

In the second example, there are no pairs.

In the third example, there are three pairs:

  • (2,3): ()()() (())();
  • (4,5): ()()() ()(());
  • (2,5): ()()() (()());

 

解题思路

  如果一个括号序列是合法的,那么必然满足以下 2 个条件:

  1. 在任意一个前缀中 ( 的数量不小于 ) 的数量。
  2. 整个序列的 ( 和 ) 数量相等。

  现在把 ( 和 ) 分别看作 11,对序列求一个前缀和,记为 si。考虑枚举反转区间的左端点 l,那么哪些右端点 r(irn) 使得将区间 [l,r] 内的括号反转后,整个括号序列仍是合法的?

  先满足第 1 个条件。如果反转区间 [l,r],那么前缀会受到影响的位置就是 k[l,r],下标为 k 的前缀会变成 sl1(sksl1)=2sl1sk。如果第条件 1 要满足,那么对于每个 k 都必须有 2sl1sk0sk2sl1,即 maxlkr{sk}2sl1。为此当固定 l 后,可以在区间 [l,n] 内二分出最远且合法的右端点 r,check 的时候需要快速知道某个区间内 si 的最大值,这个可以用 RMQ 或线段树来维护。

  最后是满足第 2 个条件。现在我们确定了最远的位置 r,但并不是所有位于 [l,r] 区间内的下标都适合作为反转区间的右端点。显然反转区间内的 ( 和 ) 的数量必须相同,因此合法的右端点 k[l,r] 还需要满足 sksl1=0。所以我们现在需要统计在 lkr 内有多少个位置满足 sk=sl1,该结果就是以 l 为左端点的合法反转区间数量。方法很简单,只需在预处理 si 时开个哈希表存储每个前缀值对应的下标。然后在前缀为 sl1 的下标中二分出大于等于 l 的最小位置 x,以及小于等于 r 的最大位置 y,合法的右端点数量就是 yx+1

  AC 代码如下,时间复杂度为 O(nlogn)

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 2e5 + 5;

char str[N];
int s[N];
int f[18][N];

int query(int l, int r) {
    int t = __lg(r - l + 1);
    return max(f[t][l], f[t][r - (1 << t) + 1]);
}

void solve() {
    scanf("%s", str + 1);
    int n = strlen(str + 1);
    map<int, vector<int>> mp;
    for (int i = 1; i <= n; i++) {
        s[i] = s[i - 1] + (str[i] == '(' ? 1 : -1);
        mp[s[i]].push_back(i);
    }
    for (int i = 0; 1 << i <= n; i++) {
        for (int j = 1; j + (1 << i) - 1 <= n; j++) {
            if (!i) f[i][j] = s[j];
            else f[i][j] = max(f[i - 1][j], f[i - 1][j + (1 << i - 1)]);
        }
    }
    LL ret = 0;
    for (int i = 1; i <= n; i++) {
        int l = i, r = n;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (query(i, mid) <= s[i - 1] << 1) l = mid;
            else r = mid - 1;
        }
        if (query(i, l) <= s[i - 1] << 1) {
            int x = lower_bound(mp[s[i - 1]].begin(), mp[s[i - 1]].end(), i) - mp[s[i - 1]].begin();
            int y = upper_bound(mp[s[i - 1]].begin(), mp[s[i - 1]].end(), l) - mp[s[i - 1]].begin() - 1;
            if (x <= y) ret += y - x + 1;
        }
    }
    printf("%lld\n", ret);
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        solve();
    }
    
    return 0;
}

  官方题解的做法与上面的思路一致,不过枚举的是反转区间的右端点 r。开一个哈希表记录 si 出现的次数,那么以 r 为右端点的合法区间数量就是哈希表中值 sr 的数量。接着把 sr 插入到哈希表中,为了使得哈希表中的 si 都满足第 1 个条件(在插入 sr 前就已经满足了),需要将哈希表中使得 si<srsisi 删除。

  AC 代码如下,时间复杂度为 O(nlogn)

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 2e5 + 5;

char s[N];

void solve() {
    scanf("%s", s + 1);
    LL ret = 0;
    map<int, int> mp;
    for (int i = 1, sum = 0; s[i]; i++) {
        sum += s[i] == '(' ? 1 : -1;
        ret += mp[sum];
        while (!mp.empty() && mp.begin()->first * 2 < sum) {
            mp.erase(mp.begin());
        }
        mp[sum]++;
    }
    printf("%lld\n", ret);
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        solve();
    }
    
    return 0;
}

 

参考资料

  Educational Codeforces Round 166 Editorial:https://codeforces.com/blog/entry/129992

posted @   onlyblues  阅读(164)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2021-05-31 KMP算法中我对获取next数组的理解
Web Analytics
点击右上角即可分享
微信分享提示