SPOJ3267 D-Query 树状数组离线操作 或 主席树 查询某一区间内有多少不同的数

xiaoxiao2021-02-28  106

D-Query

看的网上的,不甚理解。代码中有注释。维护树状数组C[i], sum(x)指 A[1:x]中有几个不同的数字

DQUERY - D-query

#sorting  #tree EnglishVietnamese

Given a sequence of n numbers a1, a2, ..., an and a number of d-queries. A d-query is a pair (i, j) (1 ≤ i ≤ j ≤ n). For each d-query (i, j), you have to return the number of distinct elements in the subsequence ai, ai+1, ..., aj.

Input

Line 1: n (1 ≤ n ≤ 30000).Line 2: n numbers a1, a2, ..., an (1 ≤ ai ≤ 106).Line 3: q (1 ≤ q ≤ 200000), the number of d-queries.In the next q lines, each line contains 2 numbers i, j representing a d-query (1 ≤ i ≤ j ≤ n).

Output

For each d-query (i, j), print the number of distinct elements in the subsequence ai, ai+1, ..., aj in a single line.

Example

Input 5 1 1 2 1 3 3 1 5 2 4 3 5 Output 3 2 3

//树状数组离线操作 #include <cstdio> #include <cstring> #include <algorithm> #include <map> #define M 200007 using namespace std; //cnt[i]存第i次询问结果,l,r分别存询问的边界,A,C是树状数组里边的 const int N = 3e4 + 7; int l[M], r[M], A[N], id[M], n; //pos[i]指数字i在A[]中的位置,拖i在A中重复出现,维护pos[i]尽量靠后 int C[N], cnt[M], pos[1000007]; //pos的大小取决于A[i]的范围,可以换为数组 inline int lowbit(int x) { return x & (-x); } void add(int x, int val) { for (int i = x; i <= n; i += lowbit(i) ) C[i] += val; } int sum(int x) { int res = 0; for (int i = x; i > 0; i -= lowbit(i)) res += C[i]; return res; } bool cmp(int i, int j) { return r[i] < r[j]; } int main() { while(~scanf("%d", &n)) { memset(C, 0, sizeof C); //树状数组 memset(pos, 0, sizeof pos); for (int i = 1; i <= n; ++i) { scanf("%d", A + i); //一维数组存数,和算法入门经典里讲的一样 add(i, 1); // if (!pos[A[i]]) pos[A[i]] = i; int m; scanf("%d", &m); for (int i = 1; i <= m; ++i) { scanf("%d%d", l + i, r + i); id[i] = i; //记下查询顺序 } //接下来根据查询的右边界对查询顺序排序 sort(id + 1, id + 1 + m, cmp); //第一次见这种操作是看acdart直播时 /*主要思想是将计数尽可能拖到后面来*/ int right = 1; for (int i = 1; i <= m; ++i) { int x = id[i]; while(right <= r[x]) { if (pos[A[right] != right]) { add(pos[A[right]], -1); //在后面出现了,前面的计数抵消。在后面计数,保证查询区间里的数不遗漏 pos[A[right]] = right; } ++right; } right = r[x]; cnt[x] = sum(r[x]) - sum(l[x] - 1); } for (int i = 1; i <= m; ++i) printf("%d\n", cnt[i]); } } return 0; }

杭电hdu4417也可以用树状数组离线操作解决

#include <cstdio> #include <cstring> #include <algorithm> #define N 100007 int bit[N], ary[N], cnt[N], n; struct Node { int val, pos; bool operator < (const Node tmp) const { return val < tmp.val; } }a[N]; struct Query { int le, ri, id, h; bool operator < (const Query tmp) const { return h < tmp.h; } }q[N]; inline int lowbit(int x) { return x & -x; } void add(int pos, int val) { for(int i = pos; i <= n; i += lowbit(i)) bit[i] += val; } int sum(int pos) { int ret = 0; for (int i = pos; i > 0; i -= lowbit(i)) ret += bit[i]; return ret; } int main() { int T, kase = 0; scanf("%d", &T); while(T-- > 0) { memset(bit, 0, sizeof bit); int m; scanf("%d%d", &n, &m); for (int i= 1; i <= n; ++i) { scanf("%d", &a[i].val); a[i].pos = i; } for (int i = 1; i <= m; ++i) { scanf("%d%d%d", &q[i].le, &q[i].ri, &q[i].h); q[i].id = i; } std::sort(a + 1, a + 1 + n); std::sort(q + 1, q + 1 + m); int k = 1; for (int i = 1; i <= m; ++i) { while(k <= n && a[k].val <= q[i].h) { add(a[k].pos, 1); ++k; } cnt[q[i].id] = sum(q[i].ri + 1) - sum(q[i].le); } printf("Case %d:\n", ++kase); for (int i = 1; i <= m; ++i) printf("%d\n", cnt[i]); } }

主席树解法

//求区间不同数的个数 #define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <algorithm> #include <cstring> #include <map> using namespace std; const int N = 30007; //最多N个数 const int M = N * 100; int n, q, tot; //n个数,q次询问 int a[N]; //原始数组 //T[i]保存[1,i]中有多少个不同的数 int T[M], lson[M], rson[M], c[M]; //c数组可能就是主席树要维护的值,比如本题维护区间数的个数 int build(int l, int r) { //返回[l,r]的父节点。建一棵非叶子节点都有两个孩子的树 int rt = tot++; //tot从0开始,用的节点数。最终tot=2*n-1 c[rt] = 0; if (l != r) { int mid = l + r >> 1; lson[rt] = build(l, mid); //lson用来保存父亲节点rt的左孩子的索引 rson[rt] = build(mid + 1, r); } return rt; } int update(int rt, int pos, int val) { int newRoot = tot++, tmp = newRoot; //newRoot申请新的节点 c[newRoot] = c[rt] + val;//主席树可以加减。同构 int l = 1, r = n; while (l < r) {//二分建树,只是写法形式上和 mid = l + r >> 1然后左右建树不同 int mid = l + r >> 1; if (pos <= mid) { lson[newRoot] = tot++;//新建左子树 rson[newRoot] = rson[rt];//右子树复用前一棵线段树的,这样就节省了空间,不至于MLE newRoot = lson[newRoot]; rt = lson[rt]; r = mid; } else { rson[newRoot] = tot++; lson[newRoot] = lson[rt]; newRoot = rson[newRoot]; //上句要用到,newRoot,so,不能慌着更新newRoot rt = rson[rt];//更新当前根节点 l = mid + 1; } c[newRoot] = c[rt] + val;//新节点的值,由之前的节点更新而来 } return tmp; //返回新建树的根节点 } //询问l到pos有多少个不同的数, rt = T[l] int query(int rt, int pos) { int res = 0; int l = 1, r = n; while (pos < r) { int mid = l + r >> 1; if (pos <= mid) {//在左子树中查 r = mid; rt = lson[rt]; } else { //在右子树中搜 res += c[lson[rt]]; rt = rson[rt]; l = mid + 1; } } return res + c[rt]; } int main() { while (~scanf("%d", &n)) { tot = 0; for (int i = 1; i <= n; ++i) scanf("%d", a + i); T[n + 1] = build(1, n); //难道是先建一棵树 map<int, int> mp; //主席树是函数式编程,可能需要映射吧 for (int i = n; i >= 1; --i) { if (mp.find(a[i]) == mp.end()) { T[i] = update(T[i + 1], i, 1); //每次调用都新建一棵线段树。update返回的是新建树的根节点 } else { int tmp = update(T[i + 1], mp[a[i]], -1); //若a[i]不是很大,可用数组代替映射 T[i] = update(tmp, i, 1); } mp[a[i]] = i; //若之前存在a[i],那么a[i]将被覆盖了,第二值保存的是较小的i } scanf("%d", &q); while (q--) { int l, r; scanf("%d%d", &l, &r); printf("%d\n", query(T[l], r));//由l确定在那棵线段树中查询 } } return 0; }

转载请注明原文地址: https://www.6miu.com/read-24466.html

最新回复(0)