跳转至

E - Infinite Parenthesis Sequence

基本信息

题目出处2018 ICPC 亚洲区域赛青岛站网络赛
队伍通过率0/1550 (0.0%)

题解

考虑计算 f(k,i) 表示 Bk 中第 i 个左括号的下标(i 可以为 0 甚至负数),设 g(k,p) 表示满足 f(k,i)p 的最大的 i,则区间 [l,r] 里左括号的总数即为 g(k,r)g(k,l1)g 的值通过二分 i 即可算出。

另外,由于我们只关心 g 的差值,因此第 0 个左括号具体对应哪个括号其实可以随便指定。方便起见,把最开始的有限字符串中的第一个左括号选为第 0 个左括号。

接下来考虑 f(k,i) 的转移方程。

  • 如果 f(k,i) 的下一个字符是右括号,因为 () -> )(,有 f(k+1,i)=f(k,i)+1
  • 如果 f(k,i) 的下一个字符是左括号,因为 (( -> (*,有 f(k+1,i)=f(k,i+1)1

综合起来,转移方程为

f(k+1,i)=min(f(k,i)+1,f(k,i+1)1)

假设 f(k,i) 是从 f(0,j) 转移过来的,那么第一维从 0 走到 k 的过程中,min 中的第二项恰选了 (ji) 次,第一项恰选了 (k(ji)) 次,因此转移方程还可以写为

f(k,i)=miniji+k(f(0,j)+k2(ji))

容易看出决策区间的长度是 (i+k)i=k。设最开始的有限字符串中,左括号的数量共有 m 个。首先分析 km 的时候如何求出最小值。

F(j)=f(0,j)2j。注意到 f(0,j+m)f(0,j)=n,有

F(j+t)=F(jmodm+t)+(n2m)×jm

其中 t 是任意非负数。因此我们可以用 RMQ 预处理 F(0)F(2m1),然后把 [i,i+k] 映射到 [imodm,imodm+k] 求最小值即可。

接下来分析 k 比较大时的解法。讨论 (n2m) 的值:

  • n2m0,说明 F(j+m)F(j),因此最小值只存在于 ij<i+m 这个范围中。
  • n2m<0,说明 F(j+m)<F(j),因此最小值只存在于 i+km<ji+k 这个范围中。

这样我们就把区间长度缩小到了 m,又可以通过 RMQ 求出最小值了。

上面还有一个细节问题没有解决:二分 g 的时候,上下界是多少?我们来看 f(k,0) 的取值范围。

  • j=0 时,有 f(k,0)f(0,0)+k<n+k,因此 g(k,l) 的最小值可能是 (n+kl)
  • 由于 f(0,j)02(ji)2k,因此 f(0,0)k,则 g(k,r) 的最大值可能是 (r+k)

所以二分上下界取 [3×109,3×109] 即可。

复杂度 O(nlogn+qlog(n+kl+r))

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <bits/stdc++.h>
#define MAXN ((int) 1e5)
#define MAXP 20
using namespace std;

int n, m, q;
char s[MAXN + 10];

int lg[MAXN + 10], rmq[MAXP][MAXN * 2 + 10];

// RMQ 预处理 F(0) 到 F(2m - 1)
void preRmq() {
    m = 0;
    for (int i = 0; i < n; i++) if (s[i] == '(') rmq[0][m++] = i;
    for (int i = m; i < m * 2; i++) rmq[0][i] = rmq[0][i - m] + n;
    for (int i = 0; i < m * 2; i++) rmq[0][i] -= i * 2;

    for (int p = 1; p < MAXP; p++) for (int i = 0, ii = (1 << p) - 1; ii < m * 2; i++, ii++) {
        int j = i + (1 << (p - 1));
        rmq[p][i] = min(rmq[p - 1][i], rmq[p - 1][j]);
    }
}

long long calc(long long K, long long i) {
    long long L, R;
    // 根据 n - 2m 的值,将区间长度缩小到 min(m, k)
    if (m * 2 <= n) {
        L = i;
        R = min(i + K, L + m - 1);
    } else {
        R = i + K;
        L = max(i, R - m + 1);
    }

    // 将区间 [L, R] 映射到 [L mod m, L mod m + R - L],再用 RMQ 求最小值
    long long div = (L >= 0 ? L / m : (L + 1) / m - 1);
    int mod = (L % m + m) % m;
    long long delta = n * div - 2 * div * m;
    int len = R - L + 1, p = lg[len];
    return min(rmq[p][mod], rmq[p][mod + len - (1 << p)]) + delta + K + 2 * i;
}

// 二分求 g(K, lim) 的值
long long query(long long K, long long lim) {
    long long head = -3e9, tail = 3e9;
    while (head < tail) {
        long long mid = (head + tail + 1) >> 1;
        if (calc(K, mid) <= lim) head = mid;
        else tail = mid - 1;
    }
    return head;
}

void solve() {
    scanf("%s", s); n = strlen(s);
    preRmq();

    scanf("%d", &q);
    while (q--) {
        long long K, l, r; scanf("%lld%lld%lld", &K, &l, &r);
        // 特殊情况:没有左括号
        if (m == 0) printf("0\n");
        else printf("%lld\n", query(K, r) - query(K, l - 1));
    }
}

int main() {
    lg[1] = 0;
    for (int i = 2; i <= MAXN; i++) lg[i] = lg[i >> 1] + 1;

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