我们做出' ( '的前缀和数组bal[ ],其中' ) '是-1
对于第四个样例:
(()())(())
我们可以看到,正则的括号序列的前缀是相等的——最开始的前一个位置和结束都为0. bal[n] == 0
内部的正则括号序列前缀也相等——bal[ l-1 ] == bal[ r ]
我们选择反转的子序列必须也是正则,只有这样bal[n]才会仍是0.也就是右边仍能匹配住,承受住
而左边能否承受住?对于我们选择的序列往左,有bal[ l-1 ]个左括号可以来承担,所以我们序列中的左括号 bal[ i ] - bal[ l-1 ] 不能比之大。(因为我们反转过去的是左括号)
如同题解所说:
此时暴力解法应该可以写出。
对于区间有无不合法前缀,变换下式子,bal[ l-1 ]*2 >= bal[ i ] ,因此我们只需要知道最大值是否不超过,这个可以用ST表来查询。
而且只要最大值不超过即可,那么我们二分快速找哪个位置不符合即可。
时间复杂度:O(logn + nlogn)
个人代码参考:
Submission #263880074 - Codeforces
#define endl "\n"
#define PII pair<int,int>
#define int long long
const int maxn = 2e5 + 5;
int arr[maxn];
int pre[maxn];
int st[maxn][22];//2^21 == 2e6
void solve()
{
string str;
cin >> str;
str = ' ' + str;
int n = str.size();
map<int, vector<int>>mm;
for (int i = 1; i < n; i++)
{
if (str[i] == ')')
pre[i] = -1;
else
pre[i] = 1;
pre[i] += pre[i - 1];
mm[pre[i]].push_back(i);
}
for (int i = 1; i < n; i++)
st[i][0] = pre[i];
for (int p = 1; p < 22; p++)
{
int len = pow(2, p);
for (int i = 1; i + len <= n; i++)
st[i][p] = max(st[i][p - 1], st[i + len / 2][p - 1]);
}
int ret = 0;
for (auto& x : mm)
{
vector<int>arr = x.second;
for (int i = 0; i < arr.size(); i++)
{
int left = arr[i];
int l = i, r = arr.size() - 1;
while (l < r)
{
int mid = (l + r+1) / 2;
int c = arr[mid];
int p = log2(c - left);
if (max(st[left + 1][p], st[(int)(c - pow(2, p) + 1)][p]) <= pre[left] * 2)
l = mid;
else
r = mid - 1;
}
ret += l-i;
}
}
cout << ret << endl;
}
官方题解:
非常短。
我们不关心位置的话,可以用map统计每个前缀有多少个。
对于当前位置,统计自己合法数目。
同时删掉自己造成的不合法的左前缀:
对于每个当前值,我们删掉不合法的左边界。
因为此后当前位置都是这个左边界形成的边界之内,都不合法了。
#include <bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
string s;
cin >> s;
map<int, int> cnt;
int b = 0;
++cnt[b];
long long ans = 0;
for (auto& c : s) {
b += (c == '(' ? +1 : -1);
ans += cnt[b];
++cnt[b];
while (cnt.begin()->first * 2 < b)
cnt.erase(cnt.begin());
}
cout << ans << '\n';
}
}