Educational Codeforces Round 166 (Rated for Div. 2) D. Invertible Bracket Sequences 题解 二分查找 ST表 双指针

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| 1lrs) 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 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 2 ⋅ 1 0 5 2 \cdot 10^5 2105.

输出描述

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;
}
  • 24
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"educational codeforces round 103 (rated for div. 2)"是一个Codeforces平台上的教育性比赛,专为2级选手设计评级。以下是有关该比赛的回答。 "educational codeforces round 103 (rated for div. 2)"是一场Codeforces平台上的教育性比赛。Codeforces是一个为程序员提供竞赛和评级的在线平台。这场比赛是专为2级选手设计的,这意味着它适合那些在算法数据结构方面已经积累了一定经验的选手参与。 与其他Codeforces比赛一样,这场比赛将由多个问题组成,选手需要根据给定的问题描述和测试用例,编写程序来解决这些问题。比赛的时限通常有两到三个小时,选手需要在规定的时间内提交他们的解答。他们的程序将在Codeforces的在线评测系统上运行,并根据程序的正确性和效率进行评分。 该比赛被称为"educational",意味着比赛的目的是教育性的,而不是针对专业的竞争性。这种教育性比赛为选手提供了一个学习和提高他们编程技能的机会。即使选手没有在比赛中获得很高的排名,他们也可以从其他选手的解决方案中学习,并通过参与讨论获得更多的知识。 参加"educational codeforces round 103 (rated for div. 2)"对于2级选手来说是很有意义的。他们可以通过解决难度适中的问题来测试和巩固他们的算法和编程技巧。另外,这种比赛对于提高解决问题能力,锻炼思维和提高团队合作能力也是非常有帮助的。 总的来说,"educational codeforces round 103 (rated for div. 2)"是一场为2级选手设计的教育性比赛,旨在提高他们的编程技能和算法能力。参与这样的比赛可以为选手提供学习和进步的机会,同时也促进了编程社区的交流与合作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值