SPOJ-DQUERY && HYSBZ 1878 HH的项链 (线段树/树状数组/莫队/主席树)

1878: [SDOI2009]HH的项链

Time Limit: 4 Sec  Memory Limit: 64 MB
 

Description

HH有一串由各种漂亮的贝壳组成的项链。HH相信不同的贝壳会带来好运,所以每次散步 完后,他都会随意取出一

段贝壳,思考它们所表达的含义。HH不断地收集新的贝壳,因此他的项链变得越来越长。有一天,他突然提出了一

个问题:某一段贝壳中,包含了多少种不同的贝壳?这个问题很难回答。。。因为项链实在是太长了。于是,他只

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

Input

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

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

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

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

N ≤ 50000,M ≤ 200000。

Output

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

Sample Input

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

Sample Output

2
2
4

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1878

题目分析:提供四种解法:1.离线线段树 2.离线树状数组 3.离线莫队 4.在线主席树
1. 离线线段树,对询问的右端点排序,维护各数字出现的最右位置, 然后就是一个区间求和问题

#include <cstdio>
#include <cstring>
#include <algorithm>
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1
using namespace std;

int const N = 5e4 + 5;
int const Q = 2e5 + 5;
int const MAX = 1e6 + 5;
int n, qnum, a[N], sum[N << 2], pos[MAX], ans[Q];

struct QUERY {
    int l, r, id;
}q[Q];

bool cmp(QUERY q1, QUERY q2) {
    if (q1.r == q2.r) {
        return q1.l < q2.l;
    }
    return q1.r < q2.r;
}

void pushUp(int rt) {
    sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}

void update(int pos, int val, int l, int r, int rt) {
    if (l == r) {
        sum[rt] += val;
        return;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid) {
        update(pos, val, lson);
    } else {
        update(pos, val, rson);
    }
    pushUp(rt);
}

int query(int L, int R, int l, int r, int rt) {
    if (L <= l && r <= R) {
        return sum[rt];
    }
    int mid = (l + r) >> 1, ans = 0;
    if (L <= mid) {
        ans += query(L, R, lson);
    }
    if (mid < R) {
        ans += query(L, R, rson);
    }
    return ans;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    scanf("%d", &qnum);
    for (int i = 0; i < qnum; i++) {
        scanf("%d %d", &q[i].l, &q[i].r);
        q[i].id = i;
    }
    sort(q, q + qnum, cmp);
    memset(pos, 0, sizeof(pos));
    int cur = 1;
    for (int i = 0; i < qnum; i++) {
        for (int j = cur; j <= q[i].r; j++) {
            if (pos[a[j]]) {
                update(pos[a[j]], -1, 1, n, 1);
            }
            update(j, 1, 1, n, 1);
            pos[a[j]] = j;
        }
        cur = q[i].r + 1;
        int left = q[i].l == 1 ? 0 : query(1, q[i].l - 1, 1, n, 1);
        ans[q[i].id] = query(1, q[i].r, 1, n, 1) - left;
    }
    for (int i = 0; i < qnum; i++) {
        printf("%d\n", ans[i]);
    }
}

2. 离线树状数组,基本同上

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int const N = 5e5 + 5;
int const Q = 2e5 + 5;
int const MAX = 1e6 + 5;
int n, qnum, a[N], sum[N], pos[MAX], ans[Q];

struct QUERY {
    int l, r, id;
}q[Q];

bool cmp(QUERY q1, QUERY q2) {
    if (q1.r == q2.r) {
        return q1.l < q2.l;
    }
    return q1.r < q2.r;
}

int lowbit(int x) {
    return x & (-x);
}

void add(int pos, int val) {
    for (int i = pos; i <= n; i += lowbit(i)) {
        sum[i] += val;
    }
}

int getsum(int pos) {
    int ans = 0;
    for (int i = pos; i > 0; i -= lowbit(i)) {
        ans += sum[i];
    }
    return ans;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    scanf("%d", &qnum);
    for (int i = 0; i < qnum; i++) {
        scanf("%d %d", &q[i].l, &q[i].r);
        q[i].id = i;
    }
    int cur = 1;
    memset(pos, 0, sizeof(pos));
    sort(q, q + qnum, cmp);
    for (int i = 0; i < qnum; i++) {
        for (int j = cur; j <= q[i].r; j++) {
            if (pos[a[j]]) {
                add(pos[a[j]], -1);
            }
            add(j, 1);
            pos[a[j]] = j;
        }
        cur = q[i].r + 1;
        ans[q[i].id] = getsum(q[i].r) - getsum(q[i].l - 1);
    }
    for (int i = 0; i < qnum; i++) {
        printf("%d\n", ans[i]);
    }
}

3. 莫队算法

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int const N = 5e4 + 5;
int const Q = 2e5 + 5;
int const MAX = 1e6 + 5;
int n, qnum, a[N], block, sum, cnt[MAX], ans[Q];

struct QUERY {
    int l, r, id; 
}q[Q];

bool cmp(QUERY q1, QUERY q2) {
    if (q1.l / block == q2.l / block) {
        return q1.r / block < q2.r / block;
    }
    return q1.l / block < q2.l / block;
}

void add(int x) {
    if (!cnt[x]) {
        sum++;
    }
    cnt[x]++;
}

void del(int x) {
    if (cnt[x] == 1) {
        sum--;
    }
    cnt[x]--;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    scanf("%d", &qnum);
    block = n / sqrt(2 * qnum / 3);
    for (int i = 0; i < qnum; i++) {
        scanf("%d %d", &q[i].l, &q[i].r);
        q[i].id = i;
    }
    sort(q, q + qnum, cmp);
    int l = 0, r = 0;
    for (int i = 0; i < qnum; i++) {
        while (l < q[i].l) {
            del(a[l++]);
        }
        while (l > q[i].l) {
            add(a[--l]);
        }
        while (r > q[i].r) {
            del(a[r--]);
        }
        while (r < q[i].r) {
            add(a[++r]);
        }
        ans[q[i].id] = sum;
    }
    for (int i = 0; i < qnum; i++){
        printf("%d\n", ans[i]);
    }
}


4. 在线主席树,维护各数字出现的最右位置,按位置建主席树,查询可以按普通区间查询的线段树一样查询根为r的线段树的[l, r]区间,也可以将[l, r]变成求[l, n],传入根为l-1的线段树和根为r的线段树,在[l, n]区间内求它们的差值 (需加读入挂)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int const N = 5e4 + 5;
int const MAX = 1e6 + 5;
int n, q, cnt, a[N], pos[MAX];
int lst[N * 30], rst[N * 30], rt[N * 30], sum[N * 30];

inline int in() {
    int x = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x;
}

void update(int pre, int &cur, int val, int pos, int l, int r) {
    cur = ++cnt;
    lst[cur] = lst[pre];
    rst[cur] = rst[pre];
    sum[cur] = sum[pre] + val;
    if (l == r) {
        return;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid) {
        update(lst[pre], lst[cur], val, pos, l, mid);
    } else {
        update(rst[pre], rst[cur], val, pos, mid + 1, r);
    }
}

int query1(int cur, int L, int R, int l, int r) {
    if (L <= l && r <= R) {
        return sum[cur];
    }
    int mid = (l + r) >> 1, ans = 0;
    if (L <= mid) {
        ans += query1(lst[cur], L, R, l, mid);
    }
    if (mid < R) {
        ans += query1(rst[cur], L, R, mid + 1, r);
    }
    return ans;
}

int query2(int pre, int cur, int L, int l, int r) {
    if (L <= l) {
        return sum[cur] - sum[pre];
    }
    int mid = (l + r) >> 1, ans = 0;
    if (L <= mid) {
        ans += query2(lst[pre], lst[cur], L, l, mid);
    }
    ans += query2(rst[pre], rst[cur], L, mid + 1, r);
    return ans;
}

int main() {
    n = in();
    int tmp = 0;
    for (int i = 1; i <= n; i++) {
        a[i] = in();
        if (pos[a[i]]) {
            update(rt[i - 1], tmp, -1, pos[a[i]], 1, n);
            update(tmp, rt[i], 1, i, 1, n);
        } else {
            update(rt[i - 1], rt[i], 1, i, 1, n);
        }
        pos[a[i]] = i;
    }
    int l, r;
    q = in();
    while (q--) {
        l = in();
        r = in();
        printf("%d\n", query1(rt[r], l, r, 1, n));
        // printf("%d\n", query2(rt[l - 1], rt[r], l, 1, n));
    }
}

 

HYSBZ-1878 时空对比
解法时间 (ms)空间 (kb)
离线线段树27488828
离线树状数组180011760
莫队算法30328060
在线主席树query1 (加快读)387228364
在线主席树query2 (加快读)327628364

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值