You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##HDU 3333 Turing Tree

一、题目大意

给定长度为 n 的数组以及 q 次询问,每次询问给出一对 l、r,输出 [l, r] 区间上所有互不相同的元素的总和

二、解题思路

问题是怎么处理那些 相同的数

我们可以一个个的修改树状数组 (或者 线段树)边修改,边求值,如果修改的位置刚好是询问的右边界,那就修改后,马上求值,有两种策略

  • ① 对问题进行排序,右区间小的先询问
  • ② 不需要进行排序,直接枚举右端点 【推荐

HASH表记录上一次相同数出现的地方

现在分两种情况:

  • 一个是,该数之前没有出现过,那就直接修改树状数组,并记录该数所在的位置
  • 另一个是,该数在之前出现过了,那就先将之前的那个数去掉(数组有记录位置),然后再放入该数,再修改该数出现位置,这样就不会出现重复计算的情况了

因为询问的右边界已经确定了,所以肯定会包含当前数的。

询问的区间有可能包含两个相同的数,也有可能只包含一个,考虑如果包含两个的情况,因为我们将前面那个去掉了,所以求得的结果就不会重复加上同一个数了,如果只包含一个的情况,那么包含的数肯定是当前出现的那个。所以去掉前面的数,并不会对后面的求解造成影响

三、树状数组解法

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <unordered_map>
#include <vector>
using namespace std;

const int N = 1e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;
int n, m;

LL a[N];
LL ans[N];

// 树状数组模板
LL c[N];

#define lowbit(x) (x & -x)

void add(int x, LL d) {
    for (int i = x; i <= n; i += lowbit(i)) c[i] += d;
}
LL query(int x) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];
    return res;
}

int main() {
    // 加快读入
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--) {
        unordered_map<int, int> b;
        vector<PII> q[N]; // 这是一个二维数组,单元格中是PII
        memset(ans, 0, sizeof ans);
        memset(c, 0, sizeof c);

        cin >> n;
        for (int i = 1; i <= n; i++) cin >> a[i];

        cin >> m;
        for (int i = 1; i <= m; i++) {
            int l, r;
            cin >> l >> r;
            q[r].push_back({l, i}); // 以r结尾的有一个查询:{id=i,l}
        }

        for (int i = 1; i <= n; i++) {
            if (b.count(a[i])) add(b[a[i]], -a[i]);
            b[a[i]] = i;
            add(i, a[i]);

            // 枚举以i为结尾的所有查询
            for (auto c : q[i]) ans[c.second] = query(i) - query(c.first - 1);
        }
        // 输出
        for (int i = 1; i <= m; i++) printf("%lld\n", ans[i]);
    }
    return 0;
}

四、线段树解法

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <unordered_map>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 30010, M = 100010;
typedef pair<int, int> PII;

// 线段树结构体
struct Node {
    int l, r;
    LL sum; // 区间和
} tr[N << 2];

int n, m;  // n个数字的原始数列m次询问
int a[N];  // 原始数组
LL ans[M]; // 问题答案

void pushup(int u) {
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void build(int u, int l, int r) {
    tr[u] = {l, r};
    int mid = (l + r) >> 1;
    if (l == r) return;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    pushup(u); // 套路其实本题不需要pushup
}

void modify(int u, int x, int v) {
    if (tr[u].l == tr[u].r) {
        tr[u].sum += v;
        return;
    }
    int mid = (tr[u].l + tr[u].r) >> 1;
    if (x <= mid) modify(u << 1, x, v);
    if (x > mid) modify(u << 1 | 1, x, v);
    pushup(u);
}

LL query(int u, int l, int r) {
    if (l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
    LL ans = 0;
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) ans += query(u << 1, l, r);
    if (r >= mid + 1) ans += query(u << 1 | 1, l, r);
    return ans;
}

int main() {
    // 加快读入
    ios::sync_with_stdio(false), cin.tie(0);
    int T;
    cin >> T;
    while (T--) {
        unordered_map<int, int> b;
        vector<PII> q[N]; // 这是一个二维数组,单元格中是PII
        memset(ans, 0, sizeof ans);
        memset(tr, 0, sizeof tr);

        cin >> n;
        // 构建线段树
        build(1, 1, n);

        for (int i = 1; i <= n; i++) cin >> a[i];

        cin >> m;
        for (int i = 1; i <= m; i++) {
            int l, r;
            cin >> l >> r;
            q[r].push_back({l, i}); // 以r结尾的有一个查询:{l,id=i}
        }

        // 图灵树
        // ① 核心:一边构建,一边查询
        // ② 理解为配合动态构建需要以当前的进度节点为右边界进行查询这导致了前面以右边界为索引保存PII信息
        for (int i = 1; i <= n; i++) {
            // 根据值利用HASH表找出此值原来的位置
            // 如果有相同值出现则旧位置上减去a[i],然后更新HASH在新位置上+a[i]
            if (b.count(a[i])) modify(1, b[a[i]], -a[i]);
            b[a[i]] = i; // 值->号
            modify(1, i, a[i]);

            // 枚举以i为右端点的查询
            for (auto c : q[i]) ans[c.second] = query(1, c.first, i);
        }
        // 输出
        for (int i = 1; i <= m; i++) printf("%lld\n", ans[i]);
    }
    return 0;
}

五、右端点排序思路

其实,与上面未排序的思想是一样的,人家上面的不排序,本质上还是按右端点排序的来的,因为它是从小到大枚举每个端点,发现当前枚举到的端点是某个查询的右边界时,开始查询,此时,它查询的都是合法的区间,后续的修改不会对其造成查询错误。

#include <iostream>
#include <unordered_map>
#include <algorithm>
#include <cstdio>
#include <cstring>

using namespace std;
typedef long long LL;
const int N = 30010, M = 100010;
/*
图灵树的来历原来是这个。。经典的线段树离线所有查询右端点排序的题目吧。。
所有询问右端点排序后从小到大扫过去线段树维护序列区间和用一个map记录各个数最右边出现的位置
一遇到一个数就把之前位置消除并更新当前位置,相当于把各个数尽量向右移动。。
*/
struct Node {
    int l, r;
    LL sum;
} tr[N << 2];

struct Question {
    int l, r, id;
    const bool operator<(const Question &t) const {
        return r < t.r;
    }
} q[M];

int n, m;
int a[N];
LL ans[M];

void pushup(int u) {
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

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

void modify(int u, int x, int d) {
    if (tr[u].l == tr[u].r) {
        tr[u].sum += (LL)d;
        return;
    }
    int mid = (tr[u].l + tr[u].r) >> 1;
    if (x <= mid) modify(u << 1, x, d);
    if (x > mid) modify(u << 1 | 1, x, d);
    pushup(u);
}

LL query(int u, int l, int r) {
    if (l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
    LL ans = 0;
    int mid = (tr[u].l + tr[u].r) >> 1;
    if (l <= mid) ans += query(u << 1, l, r);
    if (r >= mid + 1) ans += query(u << 1 | 1, l, r);
    return ans;
}

int main() {
    // 加快读入
    ios::sync_with_stdio(false), cin.tie(0);
    int T;
    cin >> T;
    while (T--) {
        unordered_map<int, int> b;
        memset(ans, 0, sizeof ans);

        cin >> n;
        for (int i = 1; i <= n; i++) cin >> a[i];
        cin >> m;

        for (int i = 1; i <= m; i++) {
            cin >> q[i].l >> q[i].r;
            q[i].id = i;
        }
        // 按右端点排序
        sort(q + 1, q + 1 + m);
        // 构建线段树
        build(1, 1, n);

        for (int i = 1, j = 1; i <= m; i++) { // i枚举每个查询,j枚举的是原始数组数据
            while (j <= q[i].r) {             // 在空间内的逐个进入,并不断修改最后的值
                if (b.count(a[j])) modify(1, b[a[j]], -a[j]);
                modify(1, j, a[j]);
                b[a[j]] = j++;
            }
            ans[q[i].id] = query(1, q[i].l, q[i].r);
        }
        for (int i = 1; i <= m; i++) printf("%lld\n", ans[i]);
    }
    return 0;
}