CF1685C Bring Balance

发布时间 2023-12-09 21:11:10作者: Hanx16Msgr

Bring Balance

Luogu CF1685C

题目描述

Alina 有一个长度为 \(2n\) 的括号序列 \(s\),由 \(n\) 个左括号 (\(n\) 个右括号 ) 组成。她想把这个括号序列变成一个平衡括号序列。

平衡括号序列定义为:能通过插入字符 +1 使之成为合法数学表达式的序列。例如,序列 (())()()(()(())) 是平衡的,而 )((()(()))( 就不是的。

在一次操作中,她可以反转 \(s\) 的任意子串。

请求出最少几次操作可将 \(s\) 转换为平衡括号序列。可以证明,这总是能在 \(n\) 次操作中完成。

\(n\le 10^5\)

Solution

考虑将括号序列转化成为折线图,( 看作是 \((+1,+1)\)) 看作是 \((+1,-1)\),那么考虑一次翻转在折线图上有什么性质。假设翻转的区间为 \([l,r]\),折线图在 \(x=l\) 处的坐标为 \((l,a)\),在 \(x=r\) 处的坐标为 \((r,b)\),那么这次翻转相当于是将 \([l,r]\) 之间的所有点关于 \((\dfrac{l+r}{2},\dfrac{a+b}{2})\) 中心对称。问题变成如何翻转使得这个折线图所有部分都在 \(x\) 轴之上。

观察发现,最多只需要两次操作就可以使得原序列符合条件。一个点 \((c,d)\) 关于 \((\dfrac{l+r}2,\dfrac{a+b}2)\) 会中心对称到 \((l+r-c,a+b-d)\)。设全局最大值的位置为 \(p\),那么翻转 \([1,p)\)\((p,2n]\) 一定是一个合法解。对于 \([1,p)\) 中的点 \((c,d)\),有 \(d\le y_p\),也就是说 \(a+y_p-d=y_p-d\ge 0\),也就意味着这样翻转后,\([1,p)\) 中的所有点都将在 \(x\) 轴之上。\((p,2n]\) 同理。

那么只需要判断是否存在 \(1\) 次操作或者原本就是合法序列的情况。不操作的情况显然好做。对于只需要一次操作的,先找到第一个和最后一个 \(y<0\) 的点 \(p_1,p_2\),那么翻转的区间 \([L,R]\) 一定有 \(L\le p_1,R\ge p_2\),并且 \(L,R\) 选的位置 \(y\) 越大越好。那么直接找 \([1,p_1)\)\((p_2,2n]\) 中的最大值出现的位置作为 \(L,R\),然后暴力验证即可。

时间复杂度 \(\mathcal O(n)\)

Code
// Cirno is not baka!
#include <bits/stdc++.h>
#define For(i, a, b) for (int i = (a); i <= (int)(b); ++i)
#define Rof(i, a, b) for (int i = (a); i >= (int)(b); --i)
#define FILE(filename) { \
    freopen(#filename ".in", "r", stdin); \
    freopen(#filename ".out", "w", stdout); \
}
#define All(x) x.begin(), x.end()
#define rAll(x) x.rbegin(), x.rend()
#define pii pair<int, int>
#define fi first
#define se second
#define i64 long long
#define mkp make_pair
// #define int long long
#define epb emplace_back
#define pb push_back
using namespace std;

const int _N = 2e5 + 5, mod = 1e9 + 7, inf = 1e9;
template<typename T> void Max(T &x, T y) {x = max(x, y);}
template<typename T> void Min(T &x, T y) {x = min(x, y);}

namespace BakaCirno {
    int N;
    int A[_N], B[_N];
    bool Check0() {
        return *min_element(A + 1, A + 2 * N + 1) >= 0;
    }
    bool Check1() {
        int p1 = 2 * N + 1, p2 = 0;
        For(i, 1, 2 * N) if (A[i] < 0)
            Min(p1, i), Max(p2, i);
        int l = max_element(A, A + p1 + 1) - A + 1;
        int r = max_element(A + p2 + 1, A + 2 * N + 1) - A;
        reverse(B + l, B + r + 1);
        partial_sum(B + 1, B + 2 * N + 1, B + 1);
        if (*min_element(B + 1, B + 2 * N + 1) >= 0) {
            cout << 1 << '\n';
            cout << l << ' ' << r << '\n';
            return 1;
        }
        return 0;
    }
    void _() {
        cin >> N;
        For(i, 1, 2 * N) {
            char c; cin >> c;
            B[i] = (c == '(' ? 1 : -1);
            A[i] = B[i] + A[i - 1];
        }
        if (Check0()) return cout << 0 << '\n', void();
        if (Check1()) return ;
        cout << 2 << '\n';
        int p = max_element(A + 1, A + 2 * N + 1) - A;
        cout << 1 << ' ' << p - 1 << '\n';
        cout << p + 1 << ' ' << 2 * N << '\n';
    }
}

signed main() {
    // FILE(test);
    cin.tie(0)->sync_with_stdio(0); int T = 1;
    cin >> T;
    while (T--) BakaCirno::_();
    // fout.flush();
}