Educational Codeforces Round 46 (Rated for Div. 2) F. One Occurrence(扫描线思想+可持久化线段树)

原题链接:F. One Occurrence


题目大意:


给出一个长度为 n n n 的数组 a a a

现在给出 q q q 次询问,每次询问一个数组区间 [ l , r ] [l, r] [l,r] (即 a l , a l + 1 , . . . , a r a_l,a_{l+1},...,a_{r} al,al+1,...,ar),如果区间内存在某个数字 a i a_i ai 只出现了一次,输出这个数字(有多个输出任意一个均可),否则输出 0 0 0

解题思路:


我们可以这么想, a i a_{i} ai 出现两次及以上当且仅当 a j = a i ( j < i ) a_{j} = a_{i}(j<i) aj=ai(j<i) ,即一个区间 [ l , r ] [l, r] [l,r] 同时包含 i , j i,j i,j 时满足。

维护某个数字出现次数超过两次以上不好维护,那么我们利用扫描线的思想或主席树的思想即可。

以下标为时间轴维护一个前缀的信息:

假设当前枚举到第 i i i 个下标,维护 a i a_{i} ai 的上一次出现位置 j j j 在什么地方。

显然这样,只要我们的询问的区间 [ l , i ] [l, i] [l,i] 包含 j j j ,即 l ≤ j < i l \leq j < i lj<i 则说明答案不合法。

反过来说,想要让答案合法,我们只需要知道是否存在某个 a j a_j aj 满足 j < l j < l j<l 即可。

那么做法显而易见,我们对每个 a i a_i ai 维护上一次出现的下标 j j j 即可。

具体而言,按照时间轴构建主席树,当前枚举到 i i i 时候,将上一次 a i a_i ai 出现的位置 j j j 在主席树中改为正无穷,下标 i i i 修改为 j j j 即可。

查询 [ l , r ] [l, r] [l,r] 时,我们在第 r r r 个版本的主席树上查询 [ l , r ] [l, r] [l,r] j j j 的最小值,假设 j < l j < l j<l 直接输出下标 j j j 对应的值,否则输出 0 0 0,注意一些细节即可。

时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)

#include <bits/stdc++.h>
using namespace std;

using PII = pair<int, int>;
using i64 = long long;

const int N = 1e6 + 1;

#define lson(x) Seg[x].l, l, mid
#define rson(x) Seg[x].r, mid + 1, r
#define val(x) Seg[x].val
#define ls(x) Seg[x].l
#define rs(x) Seg[x].r

struct SegTree {
    int l, r;
    int p, val;
} Seg[N * 40];

int idx = 0;

SegTree merge(SegTree& P, const SegTree A, const SegTree B) {
    P.p = (A.val <= B.val ? A.p : B.p);
    P.val = min(A.val, B.val);
    return P;
}

int creat(int& pre) {
    Seg[++idx] = Seg[pre];
    return idx;
}

int build(int &pre, int l, int r) {
    int cur = creat(pre);
    val(cur) = 1e9;
    if (l == r) {
        Seg[cur].p = l;
        return cur;
    }
    int mid = l + r >> 1;
    ls(cur) = build(lson(pre));
    rs(cur) = build(rson(pre));
    Seg[cur] = merge(Seg[cur], Seg[ls(cur)], Seg[rs(cur)]);
    return cur;
}

int Modify(int &pre, int l, int r, int p, int v) {
    int cur = creat(pre);
    if (l == r) {
        val(cur) = v;
        return cur;
    }
    int mid = l + r >> 1;
    if (p <= mid) {
        ls(cur) = Modify(lson(pre), p, v);
    } else {
        rs(cur) = Modify(rson(pre), p, v);
    }
    Seg[cur] = merge(Seg[cur], Seg[ls(cur)], Seg[rs(cur)]);
    return cur;
}

SegTree Query(int &k, int l, int r, int x, int y) {
    if (l >= x && r <= y) return Seg[k];
    int mid = l + r >> 1; SegTree res{};
    if (x > mid) return Query(rson(k), x, y);
    if (y <= mid) return Query(lson(k), x, y);
    return merge(res, Query(lson(k), x, y), Query(rson(k), x, y));
}

void solve() {
    int n;
    cin >> n;

    vector<int> Last(5e5 + 1), ver(n + 1);

    ver[0] = build(ver[0], 1, n);

    vector<int> a(n + 1);
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        if (Last[a[i]]) {
            ver[i] = Modify(ver[i - 1], 1, n, Last[a[i]], 1e9);
            ver[i] = Modify(ver[i], 1, n, i, Last[a[i]]);
        } else {
            ver[i] = Modify(ver[i - 1], 1, n, i, Last[a[i]]);
        }
        Last[a[i]] = i;
    }

    int q;
    cin >> q;
    for (int i = 1; i <= q; ++i) {
        int l, r;
        cin >> l >> r;
        SegTree T = Query(ver[r], 1, n, l, r);
        if (T.val < l) {
            cout << a[T.p] << '\n';
        } else {
            cout << 0 << '\n';
        }
    }
}

signed main() {

    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1; //cin >> t;
    while (t--) solve();

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值