题目链接:Codeforces 1143E

最近 Lynyrd 和 Skynyrd 来到超市购物,Lynyrd 购买了一个长度为 $n$ 的排列 $p$,Skynyrd 购买了一个长度为 $m$ 的包含 $1$ 到 $n$ 的数组 $a$。

他们给你了 $q$ 个询问,每个询问均为如下形式:“在数组 $a$ 的第 $l$ 到 $r$ 个位置之间,是否存在一个子序列满足它是 $p$ 的一个循环位移?”

对于每个询问,如果存在这样的子序列则输出 $1$ 否则输出 $0$。

数据范围:$1\le n, m, q\le 2\times 10 ^ 5$。


Solution

我们首先贪心:如果第 $i$ 个位置匹配了,那么它前一个匹配的位置一定是距离它最近的合法位置。

于是我们得到了一个朴素算法:对于 $l, r$ 里的每个数暴力往前跳,如果有任何一个数跳完 $n - 1$ 步还在区间 $[l, r]$ 内那么有解;否则无解。

接下来考虑优化这个过程。我们先不考虑区间内每个数都往前跳,只考虑第 $i$ 个数往前跳 $n - 1$ 个数的位置,这个过程可以通过倍增实现。

最后考虑区间每个数。我们用 $\text{RMQ}$ 维护区间每个数往前跳 $n - 1$ 步的最大位置,判断这个位置是否不小于 $l$ 即可。

时间复杂度:$\mathcal O(m\log n + n + q)$。


Code

#include <cstdio>
#include <algorithm>

const int N = 2e5 + 5, logN = 18;

int n, m, q, p[N], a[N], pos[N], pre[N], lg[N], f[N][logN], g[N][logN];

int query(int l, int r) {
    int k = lg[r - l + 1];
    return std::max(g[l][k], g[r - (1 << k) + 1][k]);
}
int main() {
    scanf("%d%d%d", &n, &m, &q);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &p[i]);
        pos[p[i]] = i;
    }
    p[0] = p[n];
    for (int i = 1; i <= m; i++) {
        scanf("%d", &a[i]);
        int x = p[pos[a[i]] - 1];
        f[i][0] = pre[x], pre[a[i]] = i;
    }
    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 1; i <= m; i++) {
            f[i][j] = f[f[i][j - 1]][j - 1];
        }
    }
    for (int i = 1; i <= m; i++) {
        int l = n - 1, p = i;
        for (int j = 17; ~j; j--) {
            if (l & (1 << j)) p = f[p][j];
        }
        g[i][0] = p;
    }
    for (int i = 2; i <= m; i++) lg[i] = lg[i >> 1] + 1;
    for (int j = 1; (1 << j) <= m; j++) {
        for (int i = 1; i + (1 << j) - 1 <= m; i++) {
            g[i][j] = std::max(g[i][j - 1], g[i + (1 << (j - 1))][j - 1]);
        }
    }
    for (int i = 1; i <= q; i++) {
        int l, r;
        scanf("%d%d", &l, &r);
        putchar((query(l, r) >= l) + '0');
    }
    puts("");
    return 0;
}
最后修改:2019 年 06 月 28 日
如果觉得我的文章对你有用,请随意赞赏