HH的项链 离线/在线 树状/线段树/主席树/莫队

题目

HH 有一串由各种漂亮的贝壳组成的项链。

HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。

HH 不断地收集新的贝壳,因此他的项链变得越来越长。

有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?

这个问题很难回答,因为项链实在是太长了。

于是,他只好求助睿智的你,来解决这个问题。

输入格式
第一行:一个整数 N,表示项链的长度。

第二行:N 个整数,表示依次表示项链中贝壳的编号(编号为 0 到 1000000 之间的整数)。

第三行:一个整数 M,表示 HH 询问的个数。

接下来 M 行:每行两个整数,L 和 R,表示询问的区间。

输出格式

M 行,每行一个整数,依次表示询问对应的答案。

数据范围

1≤N≤50000,
1≤M≤2×105,
1≤L≤R≤N

输入样例:

6
1 2 3 4 3 5
3
1 2
3 5
2 6

输出样例:

2
2
4

题解

离线算法

树状数组

这俩差不多, 树状更好写,

定住右区间 r 从1~n, 然后维护离 当前 r最近且没有出现过数字的位置

const int N = 5e4 + 5, M = 1e6 + 5;

int n, m, _, k;
int a[N], c[N], ls[M], ans[200005];
pair<PII, int> q[200005];

void add(int x, int k) {
    for (; x <= n; x += -x & x) c[x] += k;
}

int ask(int x) {
    int ans = 0;
    for (; x; x -= -x & x) ans += c[x];
    return ans;
}

bool cmp(const pair<PII, int>& a, const pair<PII, int>& b) {
    return a.fi.se < b.fi.se;
}

int main() {
    IOS; cin >> n;
    rep(i, 1, n) cin >> a[i];
    cin >> m;
    rep(i, 1, m) cin >> q[i].fi.fi >> q[i].fi.se, q[i].se = i;
    
    sort(q + 1, q + 1 + m, cmp);
    for (int i = 1, j = 1; i <= m; ++i) {
        for (; j <= q[i].fi.se; ++j) {
            if (ls[a[j]]) add(ls[a[j]], -1);
            add(j, 1); ls[a[j]] = j;
        }
        ans[q[i].se] = ask(q[i].fi.se) - ask(q[i].fi.fi - 1);
    }
    rep(i, 1, m) cout << ans[i] << '\n';
    return 0;
}

线段树

和树状一样

线段树就不用我敲了吧? 这道题是单点修改, 直接push_down 不需要lazy标记, 算了敲敲吧~

struct BIT {
    struct node {
        int val, l, r;
    } tr[N << 2];

    void push_up(int rt) {
        tr[rt].val = tr[rt << 1 | 1].val + tr[rt << 1].val;
    }

    void build(int rt, int l, int r) {
        tr[rt] = { 0, l, r };
        if (l == r) return;
        int mid = l + r >> 1;
        build(rt << 1, l, mid); build(rt << 1 | 1, mid + 1, r);
    }

    void change(int rt, int x, int k) {
        if (tr[rt].l == x && tr[rt].r == x) { tr[rt].val += k; return; }
        int mid = tr[rt].l + tr[rt].r >> 1;
        if (x <= mid) change(rt << 1, x, k);
        else change(rt << 1 | 1, x, k);
        push_up(rt);
    }

    int ask(int rt, int l, int r) {
        if (tr[rt].l >= l && tr[rt].r <= r) return tr[rt].val;
        int ans = 0, mid = tr[rt].l + tr[rt].r >> 1;
        if (mid >= l) ans = ask(rt << 1, l, r);
        if (r > mid) ans += ask(rt << 1 | 1, l, r);
        return ans;
    }
} bit;

bool cmp(const pair<PII, int>& a, const pair<PII, int>& b) {
    return a.fi.se < b.fi.se;
}

int main() {
    IOS; cin >> n; bit.build(1, 1, n);
    rep(i, 1, n) cin >> a[i];
    cin >> m;
    rep(i, 1, m) cin >> q[i].fi.fi >> q[i].fi.se, q[i].se = i;
    
    sort(q + 1, q + 1 + m, cmp);
    for (int i = 1, j = 1; i <= m; ++i) {
        for (; j <= q[i].fi.se; ++j) {
            if (ls[a[j]]) bit.change(1, ls[a[j]], -1);
            bit.change(1, j, 1); ls[a[j]] = j;
        }
        ans[q[i].se] = bit.ask(1, q[i].fi.fi, q[i].fi.se);
    }
    rep(i, 1, m) cout << ans[i] << '\n';
    return 0;
}

莫队

刚才是卡着 r 取修改区间, 离线

那在线怎呢办呢?

l~r 说白了就是 r - l + 1 - 重复数字的个数

怎么区分 重复数字的问题,

我们注意到 (a[i] == a[j], l < i < j < r) a[i]的下一个位置是 j, 而 j 的下一个位置 在 l~r内不存在, pre[i] 表示上一个

知道怎么算了吧, 直接记 l~r a[i] 下个位置不在l~r区间内的数的个数即可, add 和 sub 的时候判断是否要+-贡献就行了

const int N = 2e5 + 5, M = 1e6 + 5;

int n, m, _, k;
int h[M], pre[M], ne[M], ans[N];
pair<PII, int> q[N];

int add(int l, int r, int k) {
    if (k > r && pre[k] < l) return 1;
    if (k < l && ne[k] > r) return 1;
    return 0;
}

int sub(int l, int r, int k) {
    if (k == r && pre[k] < l) return 1;
    if (k == l && ne[k] > r) return 1;
    return 0;
}

int main() {
    IOS; cin >> n;
    rep(i, 1, 1e6) ne[i] = n + 1;
    rep(i, 1, n) cin >> m, pre[i] = h[m], ne[h[m]] = i, h[m] = i;
    cin >> m; int siz = sqrt(n);
    rep(i, 1, m) cin >> q[i].fi.fi >> q[i].fi.se, q[i].se = i;
    sort(q + 1, q + 1 + m, [&](pair<PII, int> a, pair<PII, int> b) {
        return a.fi.fi / siz == b.fi.fi / siz ? a.fi.se < b.fi.se : a.fi.fi / siz < b.fi.fi / siz;
    });
    int l = 1, r = 0, val = 0;
    rep(i, 1, m) {
        for (; q[i].fi.se > r; val += add(l, r, r + 1), ++r);
        for (; q[i].fi.se < r; val -= sub(l, r, r), --r);
        for (; q[i].fi.fi < l; val += add(l, r, l - 1), --l);
        for (; q[i].fi.fi > l; val -= sub(l, r, l), ++l);
        ans[q[i].se] = val;
    }
    rep(i, 1, m) cout << ans[i] << '\n';
    return 0;
}

在线算法

主席树

跟莫队思路基本一样, 去掉了pre[N]数组, 并在线静在线回答

const int N = 5e4 + 5, M = 1e6 + 5;

struct CHT {
    struct node {
        int val, lson, rson;
        node (int Val = 0, int Ls = 0, int Rs = 0)
            : val(Val), lson(Ls), rson(Rs){}
    } tr[20 * N]; //log2(n)*n

    int root[N], tot;

    void update(int &x, int y, int l, int r, int k, int cnt) {
        tr[x = ++tot] = tr[y]; tr[x].val += cnt;
        if (l == r) return;
        int mid = l + r >> 1;
        if (k <= mid) update(tr[x].lson, tr[y].lson, l, mid, k, cnt);
        else update(tr[x].rson, tr[y].rson, mid + 1, r, k, cnt);
    }

    int ask(int x, int y, int l, int r, int k) {
        if (l >= k) return tr[x].val - tr[y].val;
        int mid = l + r >> 1, ans = 0;
        if (mid >= k) ans = ask(tr[x].lson, tr[y].lson, l, mid, k);
        return ans + ask(tr[x].rson, tr[y].rson, mid + 1, r, k);
    }
} bit;

int n, m, _, k;
int h[M], ne[M];

int main() {
    IOS; cin >> n;
    rep (i, 1, 1e6) ne[i] = n + 1;
    rep(i, 1, n) cin >> m, ne[h[m]] = i, h[m] = i;
    rep(i, 1, n) bit.update(bit.root[i], bit.root[i - 1], 1, n + 1, ne[i], 1);

    cin >> m;
    rep(i, 1, m) {
        int l, r; cin >> l >> r;
        cout << bit.ask(bit.root[r], bit.root[l - 1], 1, n + 1, r + 1) << '\n';
    }
    return 0;
}
posted @ 2020-10-27 12:34  洛绫璃  阅读(194)  评论(0编辑  收藏  举报