Luogu SP3267 D-query(主席树)

SP3267 D-query

题目大意:

给出长度为 \(n\) 的序列,\(q\) 次询问,每次为区间 \([l, r]\) 有多少不同的数字。

思路:

显然可以用莫队解决,考虑带 log 的做法。

对于一个区间 \([l, r]\) 来说,区间内若有重复出现的数,那么他上一次出现的位置除了第一次出现的数以外,都在 \([l, r]\) 范围内。我们记录每个位置上的值上一次出现的位置为 \(last[i]\) ,问题转化为求 \([l, r]\)\(last[i] < l\) 的个数。区间上的权值线段树,使用主席树解决。

Code:
struct Node { //每个点维护的是值域上值的个数
    int lf, rt;
    ll sum; //该节点的左节点为hjt[lf],右节点为hjt[rt],值为sum
} hjt[N * 40];
int tot = 0, root[N];
//pre的作用是now要依赖以上一个版本的权值线段树来建立
void insert(int l, int r, int pre, int &now, ll p) {
    hjt[++tot] = hjt[pre]; //等于上一个版本线段树的当前节点
    now = tot;
    ++hjt[now].sum;
    if (l == r) return;
    int m = (l + r) >> 1;
    if (p <= m) insert(l, m, hjt[pre].lf, hjt[now].lf, p);
    else insert(m + 1, r, hjt[pre].rt, hjt[now].rt, p);
}
//搜索到的当前节点所维护的区间为[l, r]
//我们当前要查询[L, R]的权值线段树,Lnow表示L - 1版本的权值线段树遍历到的当前节点,Rnow表示R版本的权值线段树遍历到的当前节点
ll query(int l, int r, int Lnow, int Rnow, int ql, int qr) {
    if (ql <= l && r <= qr) {
        return hjt[Rnow].sum - hjt[Lnow].sum;
    }
    int m = (l + r) >> 1;
    ll ans = 0;
    if (ql <= m)
        ans += query(l, m, hjt[Lnow].lf, hjt[Rnow].lf, ql, qr);
    if (qr > m)
        ans += query(m + 1, r, hjt[Lnow].rt, hjt[Rnow].rt, ql, qr);
    return ans;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int n, m;
    cin >> n;
    vector<int> a(n + 1);
    vector<int> pre(N, 0);
    vector<int> las(n + 1);
    for (ll i = 1; i <= n; i++) {
        cin >> a[i];
        las[i] = pre[a[i]] + 1; //整体向右偏移一位
        pre[a[i]] = i;
        insert(1, n, root[i - 1], root[i], las[i]);
    }
    cin >> m;
    for (ll i = 1, l, r; i <= m; i++) {
        cin >> l >> r;
        cout << query(1, n, root[l - 1], root[r], 1, l) << "\n";
    }
    return 0;
}
posted @ 2022-03-13 10:00  Nepenthe8  阅读(13)  评论(0编辑  收藏  举报