牛客网暑期ACM多校训练营(第一场)J.Different Integers 倍增+主席树

链接:https://www.nowcoder.com/acm/contest/139/J
来源:牛客网
本文参考https://blog.csdn.net/aozil_yang/article/details/65448883

谢谢博主aozil_yang提供的思路

时间限制:C/C++ 2秒,其他语言4秒

空间限制:C/C++ 524288K,其他语言1048576K
64bit IO Format: %lld

题目描述

Given a sequence of integers a1, a2, ..., an and q pairs of integers (l1, r1), (l2, r2), ..., (lq, rq), find count(l1, r1), count(l2, r2), ..., count(lq, rq) where count(i, j) is the number of different integers among a1, a2, ..., ai, aj, aj + 1, ..., an.

输入描述:

The input consists of several test cases and is terminated by end-of-file.
The first line of each test cases contains two integers n and q.
The second line contains n integers a1, a2, ..., an.
The i-th of the following q lines contains two integers li and ri.

输出描述:

For each test case, print q integers which denote the result.

示例1

输入

3 2
1 2 1
1 2
1 3
4 1
1 2 3 4
1 3

输出

2
1
3

备注:

* 1 ≤ n, q ≤ 105
* 1 ≤ ai ≤ n
* 1 ≤ li, ri ≤ n
* The number of test cases does not exceed 10.

题目大意:

给出n个数字和q次询问,

对于每一次询问[L,R],求出[1,L]和[R,n]两个区间合并后的数字种类数有多少。

思路:

把n个数倍增,即a[i+n]=a[i]。这样的话求[1,L]和[R,n]两个区间合并后的数字种类数变成求[R,L+n]中的数字的种类数

变成了经典的主席树入门题

主席树的思路是先建立一颗空树(非必要)

关于修改

序列扫一遍,如果这个数字没有出现过,那么就直接以这个点作划分,其新的链+1表示区间上都了一种数

如果没有出现,那么找出同一个数上一次出现的位置(可以用数组标记实现)j,这样把第j个链的划分-1,并在当前链的划分+1使得区间中数字不重复,而只保留最后一个。

关于查询

如果我们要查询的区间为[L,R],那么我们就以L为划分依据,在第R个链上查询,当发现可以向左走的时候,我们直接+右儿子的sum并递归到左儿子;反之时不用加,因为会加多(因为我们都是以最后的出现的地方为划分依据的)。

代码如下

#include<iostream>
#include<string>
#include<cstring>
#include<vector>
#include<map>
#include<algorithm>
#include<queue>
#include<set>
#include<cstdio>
#include<functional>
#include<iomanip>
#include<cmath>
#include<stack>
#include<iomanip>
#include<functional>
#include <iomanip>
#include<bitset>
#define lson l,m
#define rson m+1,r
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int maxn = 2 * int(1e5) + 100;
const int BN = 30;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int mod = (int)1e9 + 7;
const double eps = 1e-6;
int read() {                    //输入挂
    int x = 0, f = 1; register char ch = getchar();
    while (ch<'0' || ch>'9') { if (ch == '-')f = -1; ch = getchar(); }
    while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x*f;
}
struct nodes {
    int l, r, sum;
}tr[maxn*40];
int root[maxn], tot;
int build(int l, int r) {
    int rt = ++tot;
    tr[rt].sum = 0;
    tr[rt].l = tr[rt].r = 0;
    if (l == r) return rt;
    int m = (l + r) >> 1;
    tr[rt].l = build(lson);
    tr[rt].r = build(rson);
    return rt;
}
int update(int pos, int c, int v, int l, int r) {
    int rt = ++tot;
    tr[rt] = tr[c];
    tr[rt].sum += v;
    if (l == r) return rt;
    int m = (l + r) >> 1;
    if (m >= pos) tr[rt].l = update(pos, tr[c].l, v, lson);
    else tr[rt].r = update(pos, tr[c].r, v, rson);
    return rt;
}
int query(int pos, int c, int l, int r) {
    if (l == r) return tr[c].sum;
    int m = (l + r) >> 1;
    if (m >= pos)return tr[tr[c].r].sum + query(pos, tr[c].l, lson);
    else return  query(pos, tr[c].r, rson);
}
int vis[maxn], num[maxn];
int main() {
    //freopen("D:\\cpp\\dates\\test.txt", "r", stdin);
    int n, q;
    while (~scanf("%d%d", &n, &q)) {
        memset(vis, -1, sizeof(vis));
        tot = 0;
        for (int i = 1; i <= n; i++) {
            num[i] = read();
            num[i + n] = num[i];
        }
        root[0] = build(1, 2 * n);
        for (int i = 1; i <= 2 * n; i++) {
            int v = num[i];
            if (vis[v] == -1)
                root[i] = update(i, root[i - 1], 1, 1, 2 * n);
            else {
                int t = update(vis[v], root[i - 1], -1, 1, 2 * n);
                root[i] = update(i, t, 1, 1, 2 * n);
            }
            vis[v] = i;
        }
        while (q--) {
            int x = read(), y = read();
            printf("%d\n", query(y, root[x + n], 1, 2 * n));
        }
    }
    return 0;
}

有一点要提醒的是,因为牛客评测姬非比赛时间时资源分散,比赛时能过的非比赛是不一定能过,所以这份代码有被t的可能(实际上均摊下来是勉强能过的)


猜你喜欢

转载自blog.csdn.net/xiuya19/article/details/81150981