洛谷 P4065 题解

题目链接

模拟赛 T1,纪念一下第一次场切紫。(话说场上这么多人切是不是都找到原了,就我这么傻想了半天)

正难则反,很容易的将题目转化为选择若干种颜色,使这些颜色在原数组中的位置连续。

设 $pre_i$ 为颜色 $i$ 最早出现的位置,$suf_i$ 为颜色 $i$ 最晚出现的位置。假设通过选择若干颜色得到的位置连续的区间为 $[l,r]$,充要条件为:

$\forall i$ $\epsilon$ $[l,r]$ $pre_i \geq l$

$\forall i$ $\epsilon$ $[l,r]$ $suf_i \leq r$

此结论可以感性理解,不想证了。

枚举一个右端点,(左端点也可以)发现 $suf_i \leq r$,$r$ 为一个定值,于是可以先二分出最小的 $l$,这里还要预处理 ``st 表``。第二个条件 $pre_i \geq l$ 就有点棘手。

其实也很简单,此时就需要用到线段树了,将区间 $[pre_i+1,i]$ 覆盖为 $0$ 即可,意思就是选了 $i$ 必须要向左延伸到 $pre_i$,否则构不成联通块。

然后就是查询,区间为二分出的 $l$ 和当前枚举的右端点 $r$。

本地跑了 $700ms$,时限 $200ms$ 居然能过就离谱了。

#include <bits/stdc++.h>
#define For(i, a, b) for (int i = (a); i <= (b); i++)
#define foR(i, a, b) for (int i = (a); i >= (b); i--)
using namespace std;
int n, x, y;
int aa[19];
int lg[300005], f[19][300005];  // ST 表
int c[300005];                  //颜色
int pre[300005], suf[300005];   //判断一个区间是否合法
int sum[1200005];
bool cover[1200005];  //线段树
inline int read() {
    char ch = getchar();
    int x = 0;
    while (ch < '0' || ch > '9') ch = getchar();
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x;
}
int qmax(int x, int y) {
    int l = lg[y - x + 1];
    return max(f[l][x], f[l][y - aa[l] + 1]);
}
void build(int l, int r, int k) {
    if (l == r) {
        sum[k] = 1;
        cover[k] = 0;
        return;
    }
    cover[k] = 0;
    int mid = l + r >> 1;
    build(l, mid, k << 1);
    build(mid + 1, r, k << 1 | 1);
    sum[k] = sum[k << 1] + sum[k << 1 | 1];
}
void pushdown(int l, int r, int k) {
    if (cover[k]) {
        cover[k << 1] = cover[k << 1 | 1] = 1;
        sum[k << 1] = sum[k << 1 | 1] = 0;
    }
}
void update(int l, int r, int k) {  //区间覆盖为 0
    if (x <= l && y >= r) {
        cover[k] = 1;
        sum[k] = 0;
        return;
    }
    pushdown(l, r, k);
    int mid = l + r >> 1;
    if (x <= mid)
        update(l, mid, k << 1);
    if (y > mid)
        update(mid + 1, r, k << 1 | 1);
    sum[k] = sum[k << 1] + sum[k << 1 | 1];
}
int query(int l, int r, int k) {
    if (x <= l && y >= r)
        return sum[k];
    pushdown(l, r, k);
    int mid = l + r >> 1, res = 0;
    if (x <= mid)
        res = query(l, mid, k << 1);
    if (y > mid)
        res += query(mid + 1, r, k << 1 | 1);
    return res;
}
void init() {
    For(i, 0, 18) aa[i] = 1 << i;
    lg[1] = 0;
    For(i, 2, 300000) lg[i] = lg[i / 2] + 1;
}
void solve() {
    n = read();
    build(1, n, 1);
    For(i, 1, n) pre[i] = 0;
    For(i, 1, n) {
        c[i] = read();
        if (!pre[c[i]])
            pre[c[i]] = i;
        suf[c[i]] = i;
    }
    For(i, 1, n) f[0][i] = suf[c[i]];
    For(j, 1, 18) for (int i = 1; i + aa[j] - 1 <= n; i++) f[j][i] =
        max(f[j - 1][i], f[j - 1][i + aa[j - 1]]);
    long long ans = 0;
    For(i, 1, n) {
        int l = i;
        foR(j, 18, 0) if (l > aa[j]) {
            int x = l - aa[j];
            if (qmax(x, i) <= i)
                l = x;
        }
        if (pre[c[i]] != i) {
            x = pre[c[i]] + 1;
            y = i;
            update(1, n, 1);
        }
        if (suf[c[i]] > i)
            continue;
        x = l;
        y = i;
        ans += query(1, n, 1);
    }
    cout << ans;
}
signed main() {
    init();
    int _ = 1;
    _ = read();
    while (_--) {
        solve();
        cout << '\n';
    }
    return 0;
}
posted @ 2024-02-16 20:23  Xy_top  阅读(17)  评论(0编辑  收藏  举报