CF380C Sereja and Brackets 题解 数列分块
题目链接:https://codeforces.com/contest/380/problem/C
题目大意:给定长度为 \(n(\le 10^6)\) 的一个括号序列,有 \(m(\le 10^5)\) 次询问,每次询问给定一个区间 \([l,r]\),你需要回答出区间 \([l,r]\) 范围内的所有子序列里面最长的合法括号表达式的长度。
解题思路:
首先,无论在哪个区间范围内,每一个 ')' 所匹配的 '(' 都是固定的。
我们可以用 \(f[i]\) 表示下标为 \(i\) 的 ')' 所匹配的 '(' 对应的下标。
对于每次询问的 \(l\) 和 \(r\),问题就变成了询问区间 \([l,r]\) 范围内存在多少 \(f[i]\) 满足 \(f[i] \ge l\)。
这个问题用 分块 是很好解决的,对于完整的分开,只需要初始时对每个分块的 \(f[i]\) 排序,然后查询的时候就可以二分查找 \(\le l\) 的元素个数。
时间复杂度是 \(O(m \cdot \sqrt{n} \cdot \log n)\),但是因为常数比较小(这里的 \(\log n\) 其实是 \(\log \sqrt{n} = \frac{1}{2} \log n\),然后不匹配的状态 \(f[i]\) 也不用保存),所以能够解决这个问题。
示例程序:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
char s[maxn];
int n, m, l, r, f[maxn];
int blo, bl[maxn];
vector<int> vec[1010];
void init() {
stack<int> stk;
for (int i = 1; i <= n; i++) {
if (s[i] == '(') stk.push(i);
else if (!stk.empty()) {
f[i] = stk.top();
stk.pop();
}
}
blo = sqrt(n);
for (int i = 1; i <= n; i++) {
bl[i] = (i - 1) / blo + 1;
if (f[i]) vec[bl[i]].push_back(f[i]);
}
for (int i = 1; i <= bl[n]; i++)
sort(vec[i].begin(), vec[i].end());
}
int solve(int l, int r) {
int cnt = 0;
for (int i = l; i <= min(bl[l]*blo, r); i++) if (f[i] >= l) cnt++;
if (bl[l] != bl[r]) {
for (int i = (bl[r]-1)*blo+1; i <= r; i++) if (f[i] >= l) cnt++;
}
for (int i = bl[l]+1; i < bl[r]; i++) {
int num = vec[i].end() - lower_bound(vec[i].begin(), vec[i].end(), l);
cnt += num;
}
return cnt * 2;
}
int main() {
scanf("%s%d", s+1, &m);
n = strlen(s+1);
init();
while (m--) {
scanf("%d%d", &l, &r);
printf("%d\n", solve(l, r));
}
return 0;
}