https://www.cnblogs.com/zyf0163/p/4749042.html 主要是看的这篇
https://blog.csdn.net/weixin_42165981/article/details/81154209
主席树一般还是开40倍吧。
http://acm.hdu.edu.cn/showproblem.php?pid=2665
求区间第K大。注意c++中&的作用。ls[]与rs[]都是通过这个更新的。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 100000 + 5; int a[N], b[N], rt[N * 20], ls[N * 20], rs[N * 20], sum[N * 20]; int n, k, tot, sz, ql, qr, x, q, T;//这些变量可以值保留tot在这命名。 void Build(int& o, int l, int r){ o = ++tot; sum[o] = 0; if(l == r) return; int m = (l + r) >> 1; Build(ls[o], l, m); Build(rs[o], m + 1, r); }
//这里的没有加val,一般就是1或-1. void update(int& o, int l, int r, int last, int p){ o = ++tot; ls[o] = ls[last]; rs[o] = rs[last]; sum[o] = sum[last] + 1; if(l == r) return; int m = (l + r) >> 1; if(p <= m) update(ls[o], l, m, ls[last], p); else update(rs[o], m + 1, r, rs[last], p); }
//rt[tt]是后面那个线段树的根,rt[ss]是前面那个线段树的根,相减就是表示中间的序列,每个根的线段树代表的是这段序列的前缀和。
int query(int ss, int tt, int l, int r, int k) { if(l == r) return l;//这个是字母l, int m = (l + r) >> 1; int cnt = sum[ls[tt]] - sum[ls[ss]];//左子树有多少个数在这段序列中 if(k <= cnt) return query(ls[ss], ls[tt], l, m, k); //说明第K大在左子树中 else return query(rs[ss], rs[tt], m + 1, r, k - cnt);//说明在右子树中,所以要减去左子树的数量。 } void work(){ scanf("%d%d%d", &ql, &qr, &x); int ans = query(rt[ql - 1], rt[qr], 1, sz, x); printf("%d ", b[ans]); } int main(){ scanf("%d", &T); while(T--){ scanf("%d%d", &n, &q); for(int i = 1; i <= n; i ++) scanf("%d", a + i), b[i] = a[i]; sort(b + 1, b + n + 1); sz = unique(b + 1, b + n + 1) - (b + 1); tot = 0; Build(rt[0],1, sz); //for(int i = 0; i <= 4 * n; i ++)printf("%d,rt = %d,ls = %d, rs = %d, sum = %d ", i, rt[i], ls[i], rs[i], sum[i]); for(int i = 1; i <= n; i ++) a[i] = lower_bound(b + 1, b + sz + 1, a[i]) - b; for(int i = 1; i <= n; i ++) update(rt[i], 1, sz, rt[i - 1], a[i]); // for(int i = 0; i <= 5 * n; i ++) printf("%d,rt = %d,ls = %d, rs = %d, sum = %d ", i, rt[i], ls[i], rs[i], sum[i]); while(q --) work(); } return 0; }
https://cn.vjudge.net/problem/HDU-6601
直接一次把前多少个找出来。如这里top = 55,表示查询前55个。
void qry(int L, int R, int l, int r) { if(cnt[R] - cnt[L] == 0 || top == M) return ; if(l == r) { rep(i, 0, cnt[R] - cnt[L]) if(top < M) sta[top++] = V[l - 1]; return ; } int mid = l + r >> 1;
qry(ls[L], ls[R], l, mid);
qry(rs[L], rs[R], mid + 1, r);
}
https://www.spoj.com/problems/DQUERY/en/
给出一段序列,然后很多查询,每次查询区间内不同数字的个数。
我们可以记录每个数字最后一次出现的位置。比如,5这个数字最后一次出现在位置3上,就把位置3记录的信息++(初始化为0)。比如有一个序列 1 2 2 1 3 那么我们记录信息的数列就是 0 0 1 1 1 (2最后出现的位置是位置3 1最后出现的位置是位置4 3最后出现的位置是位置5)。那么对区间 [1,5] , [2,5] , [3,5] , [4,5] , [5,5]的数字种数,我们都可以用sum[5]-sum[x-1]来求(sum数组记录的是前缀和)(前缀和之差可以用线段树或者树状数组来求)。
那么对着区间右端点会变化的题目,我们应该怎么办呢?先思考一下如果右端点有序的话,我们可以怎么做。对R不同的区间,向线段树或者树状数组中添加元素,知道右端点更新为新的R,在添加的过程中,如果这个元素之前出现过,就把之前记录的位置储存的信息 -1,然后在新的位置储存的信息 +1,这样就可以保证在新的右端点固定的区间里,记录的是数字最后一次出现的位置的信息,这样题目就解决了。
也就是说对于这个题目,我们也可以不用主席树,只要对询问排序,然后利用树状数组或者线段树就可以解决这个问题。(离线解法)
如果不对询问排序的话,我们就必须要用主席树来解决这个问题了,对每个右端点建立一个线段树。不断查询即可。(在线解法)
所以说这个题目虽然是主席树,但并不是基于权值线段树写的,每次是在出现的那个位置加一,而不是在那个值的位置加一,所以在区间查找个数的时候用的是普通线段树的求法。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 3e4 + 5; int a[N], b[N], rt[N * 20], ls[N * 20], rs[N * 20], sum[N * 20]; int ind[N]; int n, k, tot, sz, ql, qr, x, q, T; void Build(int& o, int l, int r){ o = ++tot; sum[o] = 0; if(l == r) return; int m = (l + r) >> 1; Build(ls[o], l, m); Build(rs[o], m + 1, r); } void update(int& o, int l, int r, int last, int p, int v){ o = ++tot; ls[o] = ls[last]; rs[o] = rs[last]; sum[o] = sum[last] + v; if(l == r) return; int m = (l + r) >> 1; if(p <= m) update(ls[o], l, m, ls[last], p, v); else update(rs[o], m + 1, r, rs[last], p, v); }
int query(int rt, int l, int r, int le, int re) {
if(le <= l && re >= r) return sum[rt];
int m = (l + r) >> 1;
if(re <= m) return query(ls[rt], l, m, le, re);
else if(le > m) return query(rs[rt], m + 1, r, le, re);
else return query(ls[rt], l, m, le, m) + query(rs[rt], m + 1, r, m + 1, re);
}
void work(){ scanf("%d%d", &ql, &qr); int ans = query(rt[qr], 1, sz, ql, qr); printf("%d ", ans); } int main(){ scanf("%d", &n); for(int i = 1; i <= n; i ++) scanf("%d", a + i), b[i] = a[i]; memset(ind, 0, sizeof(ind)); sort(b + 1, b + n + 1); sz = unique(b + 1, b + n + 1) - (b + 1); tot = 0; Build(rt[0], 1, sz); int tmp; //for(int i = 0; i <= 4 * n; i ++)printf("%d,rt = %d,ls = %d, rs = %d, sum = %d ", i, rt[i], ls[i], rs[i], sum[i]); for(int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + sz + 1, a[i]) - b; for(int i = 1; i <= n; i ++) { if(ind[a[i] ] == 0) update(rt[i], 1, sz, rt[i - 1], i, 1); else { update(tmp, 1, sz, rt[i - 1], ind[a[i]], -1); update(rt[i], 1, sz, tmp, i, 1); } ind[a[i] ] = i; } // for(int i = 0; i <= 5 * n; i ++) printf("%d,rt = %d,ls = %d, rs = %d, sum = %d ", i, rt[i], ls[i], rs[i], sum[i]); scanf("%d", &q); while(q --) work(); return 0; }
https://cn.vjudge.net/contest/243301#problem/A
这个题的意思应该是求第K大。
#include <cstdio> #include <cstring> #include <algorithm> #include<map> using namespace std; const int N = 1e5 + 5; typedef long long ll; int aa[N], bb[N], rt[N * 20], ls[N * 20], rs[N * 20], sum[N * 20]; int tot, num; struct Node { int ind; int a, b, c; Node(int ind = 0, int a = 0, int b = 0, int c = 0) : ind(ind), a(a), b(b), c(c) {}; }; Node node[2 * N]; void Build(int& o, int l, int r){ o = ++tot; sum[o] = 0; if(l == r) return; int m = (l + r) >> 1; Build(ls[o], l, m); Build(rs[o], m + 1, r); } void update(int& o, int l, int r, int last, int p, int v){ o = ++tot; ls[o] = ls[last]; rs[o] = rs[last]; sum[o] = sum[last] + v; if(l == r) return; int m = (l + r) >> 1; if(p <= m) update(ls[o], l, m, ls[last], p, v); else update(rs[o], m + 1, r, rs[last], p, v); } //查询对应序列第K大。 int querymaxk(int ss, int tt, int l, int r, int k) { if(l == r) return l; int m = (l + r) >> 1; int cnt = sum[ls[tt]] - sum[ls[ss]]; if(k <= cnt) return querymaxk(ls[ss], ls[tt], l, m, k); else return querymaxk(rs[ss], rs[tt], m + 1, r, k - cnt); } //查询值域区间有多少个数 int queryans(int root, int le, int re, int l, int r) { if(le <= l && re >= r) { return sum[root]; } int m = (l + r) >> 1; if(re <= m) { return queryans(ls[root], le, re, l, m); } else if(le > m) { return queryans(rs[root], le, re, m + 1, r); } else { return queryans(ls[root], le, m, l, m) + queryans(rs[root], m + 1, re, m + 1, r); } } int main() { int n; int ca = 0; while(~scanf("%d", &n)) { char ch[10]; int a, b, c; int cnt = 0; for(int i = 1; i <= n; i++) { scanf("%s", ch); if(ch[0] == 'I') { scanf("%d", &a); node[i] = Node(0, a, 0, 0); aa[++cnt] = a; bb[cnt] = a; } else if(ch[6] == '1') { scanf("%d%d%d", &a, &b, &c); node[i] = Node(1, a, b, c); } else if(ch[6] == '2') { scanf("%d", &a); node[i] = Node(2, a, 0, 0); } else { scanf("%d", &a); node[i] = Node(3, a, 0, 0); } } sort(aa + 1, aa + cnt + 1); tot = 0; int sz = unique(aa + 1, aa + cnt + 1) - aa - 1; Build(rt[0], 1, sz); for(int i = 1; i <= cnt; i++) { int v = lower_bound(aa + 1, aa + cnt + 1, bb[i]) - aa; update(rt[i], 1, sz, rt[i - 1], v, 1); } int val = 0; ll ans1 = 0, ans2 = 0, ans3 =0 ; for(int i = 1; i <= n; i++) { if(node[i].ind == 0) { val++; } else if(node[i].ind == 1) { int ans = querymaxk(rt[node[i].a - 1 ], rt[node[i].b ], 1, sz, node[i].c); ans1 += (ll)aa[ans]; } else if(node[i].ind == 3) { int ans = querymaxk(rt[0], rt[val], 1, sz, node[i].a); ans3 += (ll)aa[ans]; } else { int v = lower_bound(aa + 1, aa + cnt + 1, node[i].a) - aa; if(v > 1) { int ans = queryans(rt[val], 1, v, 1, sz); ans2 += (ll)ans; } else ans2++; } } printf("Case %d: ", ++ca); printf("%lld %lld %lld ", ans1, ans2, ans3); } return 0; }
https://cn.vjudge.net/contest/243301#problem/C
任务查询系统
题面
最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分。超级计算机中的
任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第Ei秒后结束(第Si秒和Ei秒任务也在运行
),其优先级为Pi。同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同。调度系统会经常向
查询系统询问,第Xi秒正在运行的任务中,优先级最小的Ki个任务(即将任务按照优先级从小到大排序后取前Ki个
)的优先级之和是多少。特别的,如果Ki大于第Xi秒正在运行的任务总数,则直接回答第Xi秒正在运行的任务优先
级之和。上述所有参数均为整数,时间的范围在1到n之间(包含1和n)。
Input
输入文件第一行包含两个空格分开的正整数m和n,分别表示任务总数和时间范围。接下来m行,每行包含三个空格
分开的正整数Si、Ei和Pi(Si≤Ei),描述一个任务。接下来n行,每行包含四个空格分开的整数Xi、Ai、Bi和Ci,
描述一次查询。查询的参数Ki需要由公式 Ki=1+(Ai*Pre+Bi) mod Ci计算得到。其中Pre表示上一次查询的结果,
对于第一次查询,Pre=1。
Output
输出共n行,每行一个整数,表示查询结果。
Sample Input
4 3 1 2 6 2 3 3 1 3 2 3 3 4 3 1 3 2 1 1 3 4 2 2 4 3
Sample Output
2 8 11
Hint
样例解释
K1 = (1*1+3)%2+1 = 1
K2 = (1*2+3)%4+1 = 2
K3 = (2*8+4)%3+1 = 3
对于100%的数据,1≤m,n,Si,Ei,Ci≤100000,0≤Ai,Bi≤100000,1≤Pi≤10000000,Xi为1到n的一个排列
每个任务有个起始时间,一个终止时间,一个优先级。
利用差分的思想,我们把任务拆分成两个,一个是起始时间加上优先级,一个是终止时间加一减去优先级,
不过主席树的叶子不是表示的时间,而是表示的优先级,按照时间排序,把优先级一个一个弄进去。
然后在找某个时间点的时候,就二分找到这个时间点的位置,即找到根节点,然后找寻前K大。
#include <cstdio> #include <cstring> #include <algorithm> #include<map> using namespace std; const int N = 2e5 + 5; typedef long long ll; int rt[N * 2], ls[N * 24], rs[N * 24], aa[N]; int num[N * 24]; ll sum[N * 24]; int tot; struct Node { int ind, val, p; Node(int ind = 0, int val = 0, int p = 0) : ind(ind), val(val), p(p) {}; bool operator < (const Node& a) const { if(ind == a.ind && val == a.val) return p < a.p; else if(ind == a.ind) return val < a.val; return ind < a.ind; } }; Node node[N * 2]; void pushup(int root) { num[root] = num[ls[root] ] + num[rs[root] ]; sum[root] = sum[ls[root] ] + sum[rs[root] ]; } void Build(int& o, int l, int r){ o = ++tot; sum[o] = 0; num[o] = 0; if(l == r) return; int m = (l + r) >> 1; Build(ls[o], l, m); Build(rs[o], m + 1, r); pushup(o); } void update(int& o, int l, int r, int last, int p, int v){ o = ++tot; ls[o] = ls[last]; rs[o] = rs[last]; if(l == r) { num[o] = num[last] + v; sum[o] = sum[last] + v * aa[p]; return; } int m = (l + r) >> 1; if(p <= m) update(ls[o], l, m, ls[last], p, v); else update(rs[o], m + 1, r, rs[last], p, v); pushup(o); } ll queryprek(int root, int l, int r, int k) { if(l == r) return min(k, num[root]) * aa[l]; int c = num[ls[root] ]; int m = (l + r) >> 1; if(k <= c) return queryprek(ls[root], l, m, k); else return sum[ls[root] ] + queryprek(rs[root], m + 1, r, k - num[ls[root] ]); } int main() { int n, m; scanf("%d%d", &n, &m); int a, b, c; for(int i = 1; i <= n; i++) { scanf("%d%d%d", &a, &b, &c); node[i * 2 - 1] = Node(a, 1, c); node[i * 2] = Node(b + 1, -1, c); aa[i] = c; } sort(node + 1, node + 2 * n + 1); sort(aa + 1, aa + n + 1); int sz = unique(aa + 1, aa + n + 1) - (aa + 1); tot = 0; Build(rt[0], 1, sz); for(int i = 1; i <= 2 * n; i++) { node[i].p = lower_bound(aa + 1, aa + n + 1, node[i].p) - aa; update(rt[i], 1, sz, rt[i - 1], node[i].p, node[i].val); } int x, A, B, C; ll pre = 1; for(int i = 1; i <= m; i++) { scanf("%d%d%d%d", &x, &A, &B, &C); int k = 1 + (A * pre + B) % C; Node y = Node(x, 1, 1 << 30); int root = lower_bound(node + 1, node + 2 * n + 1, y) - node - 1; pre = queryprek(rt[root], 1, sz, k); printf("%lld ", pre); } return 0; }
https://cn.vjudge.net/contest/243301#problem/D
题意:给出n个平行于x轴的线段,m次射击,每次射击在位置(x,0),可以射击到X=x这条线上最近的K条线段,所得分数为各线段高度和,若上一次射击分数超过P,则得分双倍。
解法:这个题目以线段的高度来建立主席树。对于线段分成开始位置与结束位置,利用差分思想,把线段添加到主席树中。这个题注意主席树开大点。
#include <cstdio> #include <cstring> #include <algorithm> #include<map> using namespace std; const int N = 1e5 + 5; typedef long long ll; int rt[2 * N], ls[N * 24], rs[N * 24], aa[N]; int num[N * 24]; ll sum[N * 24]; int tot; struct Node { int ind, val, p; Node(int ind = 0, int val = 0, int p = 0) : ind(ind), val(val), p(p) {}; bool operator < (const Node& a) const { if(ind == a.ind && val == a.val) return p < a.p; else if(ind == a.ind) return val < a.val; return ind < a.ind; } }; Node node[N * 2]; void pushup(int root) { num[root] = num[ls[root] ] + num[rs[root] ]; sum[root] = sum[ls[root] ] + sum[rs[root] ]; } void Build(int& o, int l, int r){ o = ++tot; sum[o] = 0; num[o] = 0; if(l == r) return; int m = (l + r) >> 1; Build(ls[o], l, m); Build(rs[o], m + 1, r); pushup(o); } void update(int& o, int l, int r, int last, int p, int v){ o = ++tot; ls[o] = ls[last]; rs[o] = rs[last]; if(l == r) { num[o] = num[last] + v; sum[o] = sum[last] + v * 1LL * aa[p]; return; } int m = (l + r) >> 1; if(p <= m) update(ls[o], l, m, ls[last], p, v); else update(rs[o], m + 1, r, rs[last], p, v); pushup(o); } ll queryprek(int root, int l, int r, int k) { if(l == r) return min(k, num[root]) * 1LL * aa[l]; int c = num[ls[root] ]; int m = (l + r) >> 1; if(k <= c) return queryprek(ls[root], l, m, k); else return sum[ls[root] ] + queryprek(rs[root], m + 1, r, k - num[ls[root] ]); } int n, m, x, p; int main() { while(~scanf("%d%d%d%d", &n, &m, &x, &p)) { int l, r, d; for(int i = 1; i <= n; i++) { scanf("%d%d%d", &l, &r, &d); node[2 * i - 1] = Node(l, 1, d); node[2 * i] = Node(r + 1, -1, d); aa[i] = d; } sort(node + 1, node + 2 * n + 1); sort(aa + 1, aa + n + 1); int sz = unique(aa + 1, aa + n + 1) - (aa + 1); tot = 0; Build(rt[0], 1, sz); for(int i = 1; i <= 2 * n; i++) { int k = lower_bound(aa + 1, aa + sz + 1, node[i].p) - aa; update(rt[i], 1, sz, rt[i - 1], k, node[i].val); } ll pre = 1, ans; int xx, a, b, c; while(m--) { scanf("%d%d%d%d", &xx, &a, &b, &c); int k = (a * pre + b) % c; Node y = Node(xx, 1, 1e9 + 7); int pos = lower_bound(node + 1, node + 2 * n + 1, y) - node - 1; if(k == 0 || pos == 0) { printf("0 "); pre = 0; continue; } ans = queryprek(rt[pos], 1, sz, k); if(pre > p * 1LL) ans = ans * 2; printf("%lld ", ans); pre = ans; } } return 0; }
https://cn.vjudge.net/contest/243301#problem/G
一串序列。
给定两个区间,一左一右不想交。
每个区间选一个点,求最大的中位数。
以原数列的位置建立主席树。每个位置初始为1.
然后对原数列排序,对每个数建立一个新的主席树,比他小的数的位置-1。
然后求结果时二分数的排序后的位置,求中间区间的总和,左区间的右边最大值,右区间的左边最大值,看是否加起来大于等于0.
#include<bits/stdc++.h> using namespace std; typedef pair<int, int> pii; const int N = 20000 + 10; const int inf = 0x3f3f3f3f; int rt[N], ls[N * 40], rs[N * 40], sum[N * 40], lsum[N * 40], rsum[N * 40]; int tot; void pushup(int rt) { sum[rt] = sum[ls[rt] ] + sum[rs[rt] ]; lsum[rt] = max(lsum[ls[rt] ], sum[ls[rt] ] + lsum[rs[rt] ]); rsum[rt] = max(rsum[rs[rt] ], sum[rs[rt] ] + rsum[ls[rt] ]); } void Build(int& o, int l, int r){ o = ++tot; if(l == r) { sum[o] = 1; lsum[o] = 1; rsum[o] = 1; return; } int m = (l + r) >> 1; Build(ls[o], l, m); Build(rs[o], m + 1, r); pushup(o); } //这里的没有加val,一般就是1或-1. void update(int& o, int l, int r, int last, int p, int val){ o = ++tot; ls[o] = ls[last]; rs[o] = rs[last]; if(l == r) { sum[o] = lsum[o] = rsum[o] = val; return; } int m = (l + r) >> 1; if(p <= m) update(ls[o], l, m, ls[last], p, val); else update(rs[o], m + 1, r, rs[last], p, val); pushup(o); } int querysum(int rt, int l, int r, int le, int re) { if(le <= l && re >= r) return sum[rt]; int m = (l + r) >> 1; if(re <= m) return querysum(ls[rt], l, m, le, re); else if(le > m) return querysum(rs[rt], m + 1, r, le, re); else return querysum(ls[rt], l, m, le, m) + querysum(rs[rt], m + 1, r, m + 1, re); } pii querylsum(int rt, int l, int r, int le, int re) { if(le <= l && re >= r) return pii(lsum[rt], sum[rt]); int m = (l + r) >> 1; pii tmp1 = pii(-inf, 0), tmp2 = pii(-inf, 0); if(re <= m) tmp1 = querylsum(ls[rt], l, m, le, re); else if(le > m) tmp1 = querylsum(rs[rt], m + 1, r, le, re); else { tmp1 = querylsum(ls[rt], l, m, le, m); tmp2 = querylsum(rs[rt], m + 1, r, m + 1, re); } if(tmp2.first == -inf) return tmp1; else return pii(max(tmp1.first, tmp1.second + tmp2.first), tmp1.second + tmp2.second); } pii queryrsum(int rt, int l, int r, int le, int re) { if(le <= l && re >= r) return pii(rsum[rt], sum[rt]); int m = (l + r) >> 1; pii tmp1 = pii(-inf, 0), tmp2 = pii(-inf, 0); if(re <= m) tmp1 = queryrsum(ls[rt], l, m, le, re); else if(le > m) tmp1 = queryrsum(rs[rt], m + 1, r, le, re); else { tmp1 = queryrsum(ls[rt], l, m, le, m); tmp2 = queryrsum(rs[rt], m + 1, r, m + 1, re); } if(tmp2.first == -inf) return tmp1; else return pii(max(tmp2.first, tmp2.second + tmp1.first), tmp1.second + tmp2.second); } int n, m; bool check(int mid, int a, int b, int c, int d) { int val = 0; if(c - b > 1) val = querysum(rt[mid], 0, n - 1, b + 1, c - 1); int val2 = queryrsum(rt[mid], 0, n - 1, a, b).first; int val3 = querylsum(rt[mid], 0, n - 1, c, d).first; // printf("mid = %d %d %d %d ", mid, val, val2, val3); return val + val2 + val3 >= 0; } int q[5]; pii p[N]; int main(){ scanf("%d", &n); for(int i = 0; i < n; i++) { scanf("%d", &p[i].first); p[i].second = i; } tot = 0; sort(p, p + n); Build(rt[0], 0, n - 1); for(int i = 1; i < n; i++) { update(rt[i], 0, n - 1, rt[i - 1], p[i - 1].second, -1); } scanf("%d", &m); int X = 0; while(m--) { scanf("%d%d%d%d", &q[0], &q[1], &q[2], &q[3]); q[0] = (q[0] + X) % n; q[1] = (q[1] + X) % n; q[2] = (q[2] + X) % n; q[3] = (q[3] + X) % n; sort(q, q + 4); // printf("q=%d %d %d %d ", q[0], q[1], q[2], q[3]); int l = 0, r = n - 1, mid, ans; while(l <= r) { mid = (l + r) >> 1; if(check(mid, q[0], q[1], q[2], q[3])) { l = mid + 1; ans = mid; } else r = mid - 1; } printf("%d ", p[ans].first); X = p[ans].first; } return 0; }
https://cn.vjudge.net/contest/243301#problem/J
一串序列,有四种操作
1.将某段区间的数加上某个值。版本号加一。
2查询当前某段区间和。
3.查询某个历史版本的区间和。
4将版本号回到某个以前版本,且之前那些在他后面的版本可以作废。(所以这时候可以修改tot值,节省空间)
做法。每次只记录add值,因为主席树是要继承以前版本的信息,如果按以前的区间加pushdown的话要额外增加很多节点。所以不能够每次pushdown。
每次记录add值,那么在在他上面的(即包含他的)节点要进行pushup。每次查询区间值等于sum[o] + add值,所以对于在add值下面的节点要累加add值。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 100000 + 5; typedef long long ll; int rt[N * 40], ls[N * 40], rs[N * 40]; ll sum[N * 40], add[N * 40]; int tot;//这些变量可以值保留tot在这命名。 void pushup(int rt, int m) { sum[rt] = sum[ls[rt] ] + sum[rs[rt] ] + add[ls[rt] ] * (m - (m >> 1)) + add[rs[rt] ] * (m >> 1); } void Build(int& o, int l, int r){ o = ++tot; sum[o] = 0; add[o] = 0; // printf("scs "); if(l == r) { scanf("%lld", &sum[o]); // printf("sum[%d] = %lld ", o, sum[o]); // sum[o] = a[l]; add[o] = 0; return; } int m = (l + r) >> 1; Build(ls[o], l, m); Build(rs[o], m + 1, r); // pushup(o, r - l + 1); sum[o] = sum[ls[o] ] + sum[rs[o] ]; } //这里的没有加val,一般就是1或-1. void update(int& o, int l, int r, int last, int le, int re, ll d){ o = ++tot; ls[o] = ls[last]; rs[o] = rs[last]; sum[o] = sum[last]; add[o] = add[last]; if(le <= l && re >= r) { add[o] += d; return; } int m = (l + r) >> 1; if(re <= m) update(ls[o], l, m, ls[last], le, re, d); else if(le > m) update(rs[o], m + 1, r, rs[last], le, re, d); else { update(ls[o], l, m, ls[last], le, m, d); update(rs[o], m + 1, r, rs[last], m + 1, re, d); } pushup(o, r - l + 1); // if(p <= m) update(ls[o], l, m, ls[last], p); // else update(rs[o], m + 1, r, rs[last], p); } ll query(int o, int l, int r, int le, int re, ll d) { if(le <= l && re >= r) { // printf("%d %d %lld %lld %lld ", l, r, sum[o], d, add[o]); return sum[o] + (r - l + 1) * (d + add[o]); } int m = (l + r) >> 1; if(re <= m) return query(ls[o], l, m, le, re, add[o] + d); else if(le > m) return query(rs[o], m + 1, r, le, re, add[o] + d); else { return query(ls[o], l, m, le, m, add[o] + d) + query(rs[o], m + 1, r, m + 1, re, add[o] + d); } } int main(){ int n, m; int ca = 0; while(~scanf("%d%d", &n, &m)){ tot = 0; if(ca) printf(" "); ca++; // printf("scccc "); // for(int i = 1; i <= n; i++) scanf("%lld", &a[i]); Build(rt[0], 1, n); char ch[3]; int l, r, t; ll d; int time = 0; while(m--) { scanf("%s", ch); if(ch[0] == 'C') { scanf("%d%d%lld", &l, &r, &d); time++; update(rt[time], 1, n, rt[time - 1], l, r, d); } else if(ch[0] == 'Q') { scanf("%d%d", &l, &r); printf("%lld ", query(rt[time], 1, n, l, r, 0)); } else if(ch[0] == 'H') { scanf("%d%d%d", &l, &r, &t); printf("%lld ", query(rt[t], 1, n, l, r, 0)); } else { scanf("%d", &t); time = t; tot = rt[time + 1] - 1; } } } return 0; }