[BZOJ3932] [CQOI2015]任务查询系统(主席树 || 树状数组 套 主席树 + 差分 + 离散化)
看到这个题有个很暴力的想法,
可以每一个时间点都建一颗主席树,主席树上叶子节点 i 表示优先级为 i 的任务有多少个。
当 x 到 y 有个优先级为 k 的任务时,循环 x 到 y 的每个点,都插入一个 k。
当然这样肯定完蛋。
x 到 y 插入一个优先级为 k 的任务?
想到差分,给时间点为 x 的主席树插入 k,给时间点为 y + 1 的主席树插入 -k。
那么求一个树状数组的前缀和就好了。
前缀和?
用树状数组优化。
这样就可以用 树状数组 套 主席树 来做。
——代码
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 #define LL long long 5 6 const int MAXN = 1e5 + 5, p = 200; 7 int n, m, cnt, t; 8 int S[MAXN], E[MAXN], P[MAXN], num[MAXN], root[MAXN], id[MAXN], q[21], s[MAXN * p], ls[MAXN * p], rs[MAXN * p]; 9 LL sum[MAXN * p], pre = 1; 10 11 inline int read() 12 { 13 int x = 0, f = 1; 14 char ch = getchar(); 15 for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1; 16 for(; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0'; 17 return x * f; 18 } 19 20 inline bool cmp(int x, int y) 21 { 22 return P[x] < P[y]; 23 } 24 25 inline void insert(int last, int &now, int l, int r, int x, int v1, int v2) 26 { 27 if(!now) now = ++cnt; 28 sum[now] = sum[last] + v1, s[now] = s[last] + v2, ls[now] = ls[last], rs[now] = rs[last]; 29 if(l == r) return; 30 int mid = (l + r) >> 1; 31 if(x <= mid) insert(ls[last], ls[now], l, mid, x, v1, v2); 32 else insert(rs[last], rs[now], mid + 1, r, x, v1, v2); 33 } 34 35 inline LL query(int l, int r, int k) 36 { 37 if(l == r) 38 { 39 LL t2 = 0; 40 for(int i = 1; i <= t; i++) t2 += sum[q[i]]; 41 return t2; 42 } 43 LL t2 = 0; 44 int t1 = 0, mid = (l + r) >> 1; 45 for(int i = 1; i <= t; i++) t1 += s[ls[q[i]]], t2 += sum[ls[q[i]]]; 46 if(k <= t1) 47 { 48 for(int i = 1; i <= t; i++) q[i] = ls[q[i]]; 49 return query(l, mid, k); 50 } 51 else 52 { 53 for(int i = 1; i <= t; i++) q[i] = rs[q[i]]; 54 return t2 + query(mid + 1, r, k - t1); 55 } 56 } 57 58 int main() 59 { 60 int i, j, x, a, b, c; 61 LL k; 62 n = read(); 63 m = read(); 64 for(i = 1; i <= n; i++) 65 { 66 S[i] = read(); 67 E[i] = read(); 68 P[i] = read(); 69 id[i] = i; 70 } 71 std::sort(id + 1, id + n + 1, cmp); 72 for(i = 1; i <= n; i++) num[id[i]] = i; 73 for(i = 1; i <= n; i++) 74 { 75 for(j = S[i]; j <= n; j += j & -j) insert(root[j], root[j], 1, n, num[i], P[i], 1); 76 for(j = E[i] + 1; j <= n; j += j & -j) insert(root[j], root[j], 1, n, num[i], -P[i], -1); 77 } 78 for(i = 1; i <= m; i++) 79 { 80 scanf("%d %d %d %d", &x, &a, &b, &c); 81 k = 1 + (LL)(a * pre + b) % c; 82 t = 0; 83 for(j = x; j; j -= j & -j) q[++t] = root[j]; 84 pre = query(1, n, k); 85 printf("%lld\n", pre); 86 } 87 return 0; 88 }
其实如果按照时间排序的话,依次插入主席树,就可以维护前缀和,而省去了树状数组的麻烦。
然后注意的是每个时间点有可能会有多颗主席树。
——代码
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 #define LL long long 5 6 const int MAXN = 1e5 + 5; 7 int n, m, tot, size, cnt; 8 int num[MAXN], id[MAXN], v[MAXN], ls[MAXN * 40], rs[MAXN * 40], s[MAXN * 40], root[MAXN]; 9 LL pre = 1, sum[MAXN * 40]; 10 struct node 11 { 12 int S, val, type, num; 13 }p[MAXN * 2]; 14 15 inline int read() 16 { 17 int x = 0, f = 1; 18 char ch = getchar(); 19 for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1; 20 for(; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0'; 21 return x * f; 22 } 23 24 inline bool cmp(node x, node y) 25 { 26 return x.S < y.S; 27 } 28 29 inline void insert(int &now, int l, int r, int x, int v1, int v2) 30 { 31 ++cnt; 32 sum[cnt] = sum[now] + v1; 33 s[cnt] = s[now] + v2; 34 ls[cnt] = ls[now]; 35 rs[cnt] = rs[now]; 36 now = cnt; 37 if(l == r) return; 38 int mid = (l + r) >> 1; 39 if(x <= mid) insert(ls[now], l, mid, x, v1, v2); 40 else insert(rs[now], mid + 1, r, x, v1, v2); 41 } 42 43 inline LL query(int now, int l, int r, int x) 44 { 45 if(l == r) return sum[now]; 46 int mid = (l + r) >> 1; 47 if(x <= s[ls[now]]) return query(ls[now], l, mid, x); 48 else return sum[ls[now]] + query(rs[now], mid + 1, r, x - s[ls[now]]); 49 } 50 51 inline bool cmp1(int x, int y) 52 { 53 return v[x] < v[y]; 54 } 55 56 int main() 57 { 58 int i, j, x, y, z, a; 59 LL k; 60 n = read(); 61 m = read(); 62 for(i = 1; i <= n; i++) 63 { 64 x = read(); 65 y = read(); 66 v[i] = read(); 67 num[i] = i; 68 p[++tot].S = x, p[tot].val = v[i], p[tot].type = 1; 69 p[++tot].S = y + 1, p[tot].val = -v[i], p[tot].type = -1; 70 } 71 std::sort(num + 1, num + n + 1, cmp1); 72 for(i = 1; i <= n; i++) id[num[i]] = i; 73 /*std::sort(v + 1, v + n + 1); 74 size = std::unique(v + 1, v + n + 1) - (v + 1); 75 for(i = 1; i <= n; i++) id[i] = std::lower_bound(v + 1, v + size + 1, id[i]) - v;*/ 76 tot = 0; 77 for(i = 1; i <= n; i++) p[++tot].num = id[i], p[++tot].num = id[i]; 78 std::sort(p + 1, p + tot + 1, cmp); 79 j = 1; 80 for(i = 1; i <= m; i++) 81 { 82 root[i] = root[i - 1]; 83 while(p[j].S == i) 84 insert(root[i], 1, n, p[j].num, p[j].val, p[j].type), j++; 85 } 86 for(i = 1; i <= m; i++) 87 { 88 a = read(); 89 x = read(); 90 y = read(); 91 z = read(); 92 k = 1 + (LL)(x * pre + y) % z; 93 printf("%lld\n", pre = query(root[a], 1, n, k)); 94 } 95 return 0; 96 }
最后,需要注意对动态开点的理解。
以及,这个题是对优先级离散化,优先级有可能有相同的,但是离散化却不去重,这就会使得相同数值会是递增的一段数。
为什么不去重?
这是为了方便找前 k 个。
如果去重,有可能 query 时,找到一个叶子节点它的个数会超过一个,比如说 5 个,而只要找 3 个,那样处理就比较麻烦,还得再记录每个叶子节点的优先级。
不去重就保证了每个叶节点的个数只有一个,而对于答案没有影响。