树状数组

241. 楼兰图腾

在完成了分配任务之后,西部 314 来到了楼兰古城的西部。
相传很久以前这片土地上(比楼兰古城还早)生活着两个部落,一个部落崇拜尖刀(V),一个部落崇拜铁锹(∧),他们分别用 V 和 ∧ 的形状来代表各自部落的图腾。
西部 314 在楼兰古城的下面发现了一幅巨大的壁画,壁画上被标记出了 n 个点,经测量发现这 n 个点的水平位置和竖直位置是两两不同的。
西部 314 认为这幅壁画所包含的信息与这 n 个点的相对位置有关,因此不妨设坐标分别为 (1,y1),(2,y2),…,(n,yn),其中 y1∼yn 是 1 到 n 的一个排列。
西部 314 打算研究这幅壁画中包含着多少个图腾。
如果三个点 (i,yi),(j,yj),(k,yk) 满足 1≤iyj,yj如果三个点 (i,yi),(j,yj),(k,yk) 满足 1≤iyk,则称这三个点构成 ∧ 图腾;
西部314 想知道,这 n 个点中两个部落图腾的数目。
因此,你需要编写一个程序来求出 V 的个数和 ∧ 的个数。
输入格式
第一行一个数 n。
第二行是 n 个数,分别代表 y1,y2,…,yn。
输出格式
两个数,中间用空格隔开,依次为 V 的个数和 ∧ 的个数。
数据范围
对于所有数据,n≤200000,且输出答案不会超过 int64。
y1∼yn 是 1 到 n 的一个排列。
输入样例:
5
1 5 3 2 4
输出样例:
3 4
image.png
image.png

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. using namespace std;
  5. const int N = 2000010;
  6. typedef long long LL;
  7. int n;
  8. //t[i]表示树状数组i结点覆盖的范围和
  9. int a[N], t[N];
  10. //Lower[i]表示左边比第i个位置小的数的个数
  11. //Greater[i]表示左边比第i个位置大的数的个数
  12. int Lower[N], Greater[N];
  13. //返回非负整数x在二进制表示下最低位1及其后面的0构成的数值
  14. int lowbit(int x)
  15. {
  16. return x & -x;
  17. }
  18. //将序列中第x个数加上k。
  19. void add(int x, int k)
  20. {
  21. for(int i = x; i <= n; i += lowbit(i)) t[i] += k;
  22. }
  23. //查询序列前x个数的和
  24. int ask(int x)
  25. {
  26. int sum = 0;
  27. for(int i = x; i; i -= lowbit(i)) sum += t[i];
  28. return sum;
  29. }
  30. int main()
  31. {
  32. scanf("%d", &n);
  33. for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
  34. //从左向右,依次统计每个位置左边比第i个数y小的数的个数、以及大的数的个数
  35. for(int i = 1; i <= n; i++)
  36. {
  37. int y = a[i]; //第i个数
  38. //在前面已加入树状数组的所有数中统计在区间[1, y - 1]的数字的出现次数
  39. Lower[i] = ask(y - 1);
  40. //在前面已加入树状数组的所有数中统计在区间[y + 1, n]的数字的出现次数
  41. Greater[i] = ask(n) - ask(y);
  42. //将y加入树状数组,即数字y出现1次
  43. add(y, 1);
  44. }
  45. //清空树状数组,从右往左统计每个位置右边比第i个数y小的数的个数、以及大的数的个数
  46. memset(t, 0, sizeof t);
  47. LL resA = 0, resV = 0;
  48. //从右往左统计
  49. for(int i = n; i >= 1; i--)
  50. {
  51. int y = a[i];
  52. resA += (LL)Lower[i] * ask(y - 1);
  53. resV += (LL)Greater[i] * (ask(n) - ask(y));
  54. //将y加入树状数组,即数字y出现1次
  55. add(y, 1);
  56. }
  57. printf("%lld %lld\n", resV, resA);
  58. return 0;
  59. }
  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. typedef long long LL;
  5. using namespace std;
  6. const int N = 2e5 + 10;
  7. int a[N], tr[N];
  8. int n;
  9. LL Greate[N], Lower[N];
  10. int lowbit(int x) {
  11. return x & -x;
  12. }
  13. void update(int x, int c) // 位置x加c
  14. {
  15. for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
  16. }
  17. int query(int x) // 返回前x个数的和
  18. {
  19. int res = 0;
  20. for (int i = x; i; i -= lowbit(i)) res += tr[i];
  21. return res;
  22. }
  23. int main() {
  24. cin >> n;
  25. for (int i = 1; i <= n; i++) {
  26. cin >> a[i];
  27. }
  28. for (int i = 1; i <= n; i++) {
  29. int y = a[i];
  30. Greate[i] = query(n) - query(y);
  31. Lower[i] = query(y - 1);
  32. update(y, 1);
  33. }
  34. memset(tr, 0, sizeof tr);
  35. LL resA = 0, resV = 0;
  36. for (int i = n; i >= 1; i--) {
  37. int y = a[i];
  38. resA += Lower[i] * query(y - 1);
  39. resV += Greate[i] * (query(n) - query(y));
  40. update(y, 1);
  41. }
  42. cout << resV << " " << resA << endl;
  43. }

242. 一个简单的整数问题

给定长度为 N 的数列 A,然后输入 M 行操作指令。
第一类指令形如 C l r d,表示把数列中第 l∼r 个数都加 d。
第二类指令形如 Q x,表示询问数列中第 x 个数的值。
对于每个询问,输出一个整数表示答案。
输入格式
第一行包含两个整数 N 和 M。
第二行含 N 个整数 A[i]。
接下来 M 行表示 M 条指令,每条指令的格式如题目描述所示。
输出格式
对于每个询问,输出一个整数表示答案。
每个答案占一行。
数据范围
1≤N,M≤10^5,
|d|≤10000,
|A[i]|≤10^9
输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
Q 4
Q 1
Q 2
C 1 6 3
Q 2
输出样例:
4
1
2
5

image.png

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. typedef long long LL;
  5. using namespace std;
  6. const int N = 1e5 + 10;
  7. int a[N], n, m;
  8. int tr[N];
  9. int lowbit(int x) {
  10. return x & -x;
  11. }
  12. void update(int x, int c) // 位置x加c
  13. {
  14. for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
  15. }
  16. LL query(int x) // 返回前x个数的和
  17. {
  18. LL res = 0;
  19. for (int i = x; i; i -= lowbit(i)) res += tr[i];
  20. return res;
  21. }
  22. int main() {
  23. cin >> n >> m;
  24. for (int i = 1; i <= n; i++) {
  25. cin >> a[i];
  26. }
  27. for (int i = 1; i <= n; i++) {
  28. update(i, a[i] - a[i - 1]); // 存入差分序列
  29. }
  30. while (m--) {
  31. char op[2];
  32. int l, r, d, x;
  33. scanf("%s", &op);
  34. if (op[0] == 'C') {
  35. scanf("%d%d%d", &l, &r, &d);
  36. update(l, d); // 差分修改
  37. update(r + 1, -d);
  38. } else {
  39. scanf("%d", &x);
  40. cout << query(x) << endl;
  41. }
  42. }
  43. }

244. 谜一样的牛

有 n 头奶牛,已知它们的身高为 1∼n 且各不相同,但不知道每头奶牛的具体身高。
现在这 n 头奶牛站成一列,已知第 i 头牛前面有 Ai 头牛比它低,求每头奶牛的身高。
输入格式
第 1 行:输入整数 n。
第 2..n 行:每行输入一个整数 Ai,第 i 行表示第 i 头牛前面有 Ai 头牛比它低。
(注意:因为第 1 头牛前面没有牛,所以并没有将它列出)
输出格式
输出包含 n 行,每行输出一个整数表示牛的身高。
第 i 行输出第 i 头牛的身高。
数据范围
1≤n≤10^5
输入样例:
5
1
2
1
0
输出样例:
2
4
5
3
1
image.png

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. using namespace std;
  5. const int N = 1e5 + 10;
  6. int h[N], n;
  7. int tr[N];
  8. int ans[N];
  9. int lowbit(int x) {
  10. return x & -x;
  11. }
  12. void update(int x, int c) // 位置x加c
  13. {
  14. for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
  15. }
  16. int query(int x) // 返回前x个数的和
  17. {
  18. int res = 0;
  19. for (int i = x; i; i -= lowbit(i)) res += tr[i];
  20. return res;
  21. }
  22. int main() {
  23. cin >> n;
  24. for (int i = 2; i <= n; i++) {
  25. cin >> h[i];
  26. }
  27. for (int i = 1; i <= n; i++) {
  28. update(i, 1); //每个位置插入1
  29. }
  30. for (int i = n; i; i--) {
  31. int k = h[i] + 1;
  32. int l = 1, r = n;
  33. while (l < r) { // 在没有用过的数当中 二分找第k个数
  34. int mid = l + r >> 1;
  35. if (query(mid) >= k) {
  36. r = mid;
  37. } else {
  38. l = mid + 1;
  39. }
  40. }
  41. ans[i] = l;
  42. update(l, -1); // l这个位置的数已经用过,update(l,-1)相当于将这个位置为标记为用过
  43. }
  44. for (int i = 1; i <= n; i++) {
  45. cout << ans[i] << endl;
  46. }
  47. }

4316. 合适数对

给定一个长度为 n 的整数数列 a1,a2,…,an 和一个整数 t。
请你判断共有多少个数对 (l,r) 同时满足:
1≤l≤r≤n
al+al+1+…+ar−1+ar输入格式
第一行包含两个整数 n 和 t。
第二行包含 n 个整数 a1,a2,…,an。
输出格式
一个整数,表示满足条件的数对的数量。
数据范围
前三个测试点满足 1≤n≤5。
所有测试点满足 1≤n≤2×10^5,|t|≤2×1014,|ai|≤109。
输入样例1:
5 4
5 -1 3 4 -1
输出样例1:
5
输入样例2:
3 0
-1 2 -3
输出样例2:
4
输入样例3:
4 -1
-2 1 -2 3
输出样例3:
3

  1. // 暴力解法,直接前缀和
  2. #include <iostream>
  3. #include <cstring>
  4. #include <algorithm>
  5. using namespace std;
  6. typedef long long LL;
  7. int n,m;
  8. const int N = 2e5+10;
  9. LL a[N],s[N];
  10. int res;
  11. int main()
  12. {
  13. cin >> n >> m;
  14. for (int i = 1; i <= n; i ++ ){
  15. cin >> a[i];
  16. s[i] = s[i-1]+a[i];
  17. }
  18. for (int i = 1; i <= n; i ++ ){
  19. for (int j = i; j <= n; j ++ ){
  20. if(s[j]-s[i-1]<m){
  21. res++;
  22. }
  23. }
  24. }
  25. cout << res;
  26. }
  1. // 树状数组
  2. #include <iostream>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <vector>
  6. using namespace std;
  7. typedef long long LL;
  8. const int N = 2e6 + 10, M = N * 2;
  9. LL n, m;
  10. LL s[N],cnt;
  11. LL tr[M];
  12. vector<LL> nums;
  13. int lowbit(int x) {
  14. return x & -x;
  15. }
  16. void add(int x, int k) {
  17. for (int i = x; i <= cnt; i += lowbit(i)) tr[i] += k;
  18. }
  19. int sum(int x) {
  20. int res = 0;
  21. for (int i = x; i; i -= lowbit(i)) res += tr[i];
  22. return res;
  23. }
  24. int get(LL x) {
  25. return lower_bound(nums.begin(), nums.end(), x) - nums.begin() + 1;
  26. }
  27. int main() {
  28. cin >> n >> m;
  29. for (int i = 1; i <= n; i++) {
  30. cin >> s[i];
  31. s[i] += s[i - 1];
  32. }
  33. for (int i = 0; i <= n; i++) { //将所有前缀和以及后续询问用到的数值全部离散化
  34. nums.push_back(s[i]);
  35. nums.push_back(s[i] - m);
  36. }
  37. sort(nums.begin(), nums.end());
  38. nums.erase(unique(nums.begin(), nums.end()), nums.end());
  39. // 把每一个数 添加到树状数组中
  40. LL res = 0;
  41. cnt = nums.size();
  42. add(get(s[0]), 1);
  43. for (int i = 1; i <= n; i++) {
  44. res += sum(cnt) - sum(get(s[i] - m));
  45. add(get(s[i]), 1);
  46. }
  47. cout << res;
  48. }

线段树

1275. 最大数

给定一个正整数数列 a1,a2,…,an,每一个数都在 0∼p−1 之间。
可以对这列数进行两种操作:
添加操作:向序列后添加一个数,序列长度变成 n+1;
询问操作:询问这个序列中最后 L 个数中最大的数是多少。
程序运行的最开始,整数序列为空。
一共要对整数序列进行 m 次操作。
写一个程序,读入操作的序列,并输出询问操作的答案。
输入格式
第一行有两个正整数 m,p,意义如题目描述;
接下来 m 行,每一行表示一个操作。
如果该行的内容是 Q L,则表示这个操作是询问序列中最后 L 个数的最大数是多少;
如果是 A t,则表示向序列后面加一个数,加入的数是 (t+a) mod p。其中,t 是输入的参数,a 是在这个添加操作之前最后一个询问操作的答案(如果之前没有询问操作,则 a=0)。
第一个操作一定是添加操作。对于询问操作,L>0 且不超过当前序列的长度。
输出格式
对于每一个询问操作,输出一行。该行只有一个数,即序列中最后 L 个数的最大数。
输入样例:
10 100
A 97
Q 1
Q 1
A 17
Q 2
A 63
Q 1
Q 1
Q 3
A 99
输出样例:
97
97
97
60
60
97
样例解释
最后的序列是 97,14,60,96。

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #include <algorithm>
  5. using namespace std;
  6. typedef long long LL;
  7. const int N = 200010;
  8. int m, p;
  9. struct Node
  10. {
  11. int l, r;
  12. int v; // 区间[l, r]中的最大值
  13. }tr[N * 4];
  14. void pushup(int u) // 由子节点的信息,来计算父节点的信息
  15. {
  16. tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
  17. }
  18. void build(int u, int l, int r)
  19. {
  20. tr[u] = {l, r};
  21. if (l == r) return;
  22. int mid = l + r >> 1;
  23. build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
  24. }
  25. int query(int u, int l, int r)
  26. {
  27. if (tr[u].l >= l && tr[u].r <= r) return tr[u].v; // 树中节点,已经被完全包含在[l, r]中了
  28. int mid = tr[u].l + tr[u].r >> 1;
  29. int v = 0;
  30. if (l <= mid) v = query(u << 1, l, r);
  31. if (r > mid) v = max(v, query(u << 1 | 1, l, r));
  32. return v;
  33. }
  34. void modify(int u, int x, int v)
  35. {
  36. if (tr[u].l == x && tr[u].r == x) tr[u].v = v;
  37. else
  38. {
  39. int mid = tr[u].l + tr[u].r >> 1;
  40. if (x <= mid) modify(u << 1, x, v);
  41. else modify(u << 1 | 1, x, v);
  42. pushup(u);
  43. }
  44. }
  45. int main()
  46. {
  47. int n = 0, last = 0;
  48. scanf("%d%d", &m, &p);
  49. build(1, 1, m);
  50. int x;
  51. char op[2];
  52. while (m -- )
  53. {
  54. scanf("%s%d", op, &x);
  55. if (*op == 'Q')
  56. {
  57. last = query(1, n - x + 1, n);
  58. printf("%d\n", last);
  59. }
  60. else
  61. {
  62. modify(1, n + 1, ((LL)last + x) % p);
  63. n ++ ;
  64. }
  65. }
  66. return 0;
  67. }

245. 你能回答这些问题吗

给定长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
1 x y,查询区间 [x,y] 中的最大连续子段和,即 maxx≤l≤r≤y{∑i=lrA[i]}。
2 x y,把 A[x] 改成 y。
对于每个查询指令,输出一个整数表示答案。
输入格式
第一行两个整数 N,M。
第二行 N 个整数 A[i]。
接下来 M 行每行 3 个整数 k,x,y,k=1 表示查询(此时如果 x>y,请交换 x,y),k=2 表示修改。
输出格式
对于每个查询指令输出一个整数表示答案。
每个答案占一行。
输入样例:
5 3
1 2 -3 4 5
1 2 3
2 2 -1
1 3 2
输出样例:
2
-1

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. using namespace std;
  5. const int N = 5e5+10;
  6. int w[N];
  7. struct Node
  8. {
  9. int l, r;
  10. int sum; // a[l]...a[r]的和
  11. int lmax,rmax,tmax; // 以l为起点的最大连续子段和的长度
  12. }tr[N * 4];
  13. /*
  14. 分治法求最大连续子段和
  15. */
  16. void pushup(Node &u,Node &l,Node &r){
  17. u.sum = l.sum + r.sum;
  18. u.lmax = max(l.lmax,l.sum+r.lmax);
  19. u.rmax = max(r.rmax,r.sum + l.rmax);
  20. u.tmax = max(max(l.tmax,r.tmax),l.rmax + r.lmax);
  21. }
  22. void pushup(int u)
  23. {
  24. pushup(tr[u],tr[u<<1],tr[u<<1|1]); // 这里要写|不要写成+
  25. }
  26. void build(int u, int l, int r)
  27. {
  28. if (l == r) tr[u] = {l, r,w[l],w[l],w[l],w[l]};
  29. else
  30. {
  31. tr[u] = {l, r};
  32. int mid = l + r >> 1;
  33. build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
  34. pushup(u);
  35. }
  36. }
  37. void modify(int u, int x, int v){ // 将x节点修改为v
  38. if(tr[u].l==x&&tr[u].r==x){
  39. tr[u] = {x,x,v,v,v,v};
  40. return;
  41. }
  42. int mid = tr[u].l+tr[u].r >> 1;
  43. if(x<=mid){
  44. modify(u<<1,x,v);
  45. }else{
  46. modify(u<<1|1,x,v);
  47. }
  48. pushup(u);
  49. }
  50. Node query(int u, int l, int r)
  51. {
  52. if (tr[u].l >= l && tr[u].r <= r)
  53. {
  54. return tr[u];
  55. }
  56. else
  57. {
  58. int mid = tr[u].l + tr[u].r >> 1;
  59. if(r<=mid){
  60. return query(u<<1,l,r);
  61. }else if(l>mid){
  62. return query(u<<1|1,l,r);
  63. }else{
  64. Node res;
  65. auto t1 = query(u<<1,l,r);
  66. auto t2 = query(u<<1|1,l,r);
  67. pushup(res,t1,t2);
  68. return res;
  69. }
  70. }
  71. }
  72. int main(){
  73. int n,m;
  74. cin >> n >> m;
  75. for (int i = 1; i <= n; i ++ ){
  76. cin >> w[i];
  77. }
  78. build(1,1,n);
  79. while (m -- ){
  80. int t,x,y;
  81. cin >> t >> x >> y;
  82. if(t==1){
  83. if(x>y){
  84. swap(x,y);
  85. }
  86. auto t = query(1,x,y);
  87. cout << t.tmax <<endl;
  88. }else{
  89. modify(1,x,y);
  90. }
  91. }
  92. }

246. 区间最大公约数

给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
Q l r,表示询问 A[l],A[l+1],…,A[r] 的最大公约数(GCD)。
对于每个询问,输出一个整数表示答案。
输入格式
第一行两个整数 N,M。
第二行 N 个整数 A[i]。
接下来 M 行表示 M 条指令,每条指令的格式如题目描述所示。
输出格式
对于每个询问,输出一个整数表示答案。
image.png

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. typedef long long LL;
  5. const int N = 5e5+10;
  6. using namespace std;
  7. LL w[N];
  8. int n,m;
  9. LL gcd(LL a, LL b) // 欧几里得算法
  10. {
  11. return b ? gcd(b, a % b) : a;
  12. }
  13. struct Node
  14. {
  15. int l, r;
  16. LL sum; // l,r区间的和
  17. LL d; // l,r区间的最大公约数
  18. }tr[N * 4];
  19. void pushup(Node &u, Node &l, Node &r){
  20. u.sum = l.sum+r.sum;
  21. u.d = gcd(l.d,r.d);
  22. }
  23. void pushup(int u)
  24. {
  25. pushup(tr[u],tr[u<<1],tr[u<<1|1]);
  26. }
  27. void build(int u, int l, int r)
  28. {
  29. if (l == r) {
  30. LL b = w[l]-w[l-1]; // 这里关键
  31. tr[u] = {l, r, b, b};
  32. }
  33. else
  34. {
  35. tr[u] = {l, r};
  36. int mid = l + r >> 1;
  37. build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
  38. pushup(u);
  39. }
  40. }
  41. Node query(int u, int l, int r)
  42. {
  43. if (tr[u].l >= l && tr[u].r <= r)
  44. {
  45. return tr[u];
  46. }
  47. else
  48. {
  49. int mid = tr[u].l + tr[u].r >> 1;
  50. if(r<=mid){
  51. return query(u<<1,l,r);
  52. }else if(l>mid){
  53. return query(u<<1|1,l,r);
  54. }else{
  55. Node res,t1,t2;
  56. t1 = query(u<<1, l, r);
  57. t2 = query(u<<1|1, l, r);
  58. pushup(res, t1, t2);
  59. return res;
  60. }
  61. }
  62. }
  63. void modify(int u,int x,LL v){
  64. if(tr[u].l==x&&tr[u].r==x){
  65. LL b = tr[u].sum + v;
  66. tr[u] = {x,x,b,b};
  67. }else{
  68. int mid = tr[u].l + tr[u].r >>1;
  69. if(x<=mid){
  70. modify(u<<1,x,v);
  71. }else{
  72. modify(u<<1|1,x,v);
  73. }
  74. pushup(u);
  75. }
  76. }
  77. int main()
  78. {
  79. cin >> n >> m;
  80. for (int i = 1; i <= n; i ++ ){
  81. cin >> w[i];
  82. }
  83. build(1,1,n);
  84. char op[5];
  85. int l,r;
  86. LL d;
  87. while (m -- ){
  88. scanf("%s", &op);
  89. if(op[0]=='C'){
  90. cin >> l >> r >>d;
  91. modify(1,l,d);
  92. if(r+1<=n){
  93. modify(1,r+1,-d);
  94. }
  95. }else{
  96. cin >> l >> r;
  97. Node t1={0,0,0,0};
  98. auto t2 = query(1,1,l);
  99. if(l+1<=r){
  100. t1 = query(1,l+1,r);
  101. }
  102. /* 这里为什么要求一个区间[1,l]的区间和呢,看下面解释
  103. */
  104. cout << abs(gcd(t2.sum,t1.d))<<endl;
  105. }
  106. }
  107. }
  108. /*
  109. 差分序列
  110. 原数列 w[1] w[2] w[3] w[4] w[5]
  111. 差分数列w[1]-w[0] w[2]-w[1] w[3]-w[2] w[4]-w[3] w[5]-w[4]
  112. 差分数列b[1] b[2] b[3] b[4] b[5]
  113. 假如现在目标是求[2,4]区间的最大公约数
  114. 那么不能直接查询query(1,2,4)的公约数,因为 差分序列b[2]不仅包含了w[2],还包含了w[1]
  115. 所以就求从1到l的前缀和,得出w[2]的值
  116. */

243. 一个简单的整数问题2

给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
Q l r,表示询问数列中第 l∼r 个数的和。
对于每个询问,输出一个整数表示答案。
输入格式
第一行两个整数 N,M。
第二行 N 个整数 A[i]。
接下来 M 行表示 M 条指令,每条指令的格式如题目描述所示。
输出格式
对于每个询问,输出一个整数表示答案。
每个答案占一行。
输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
输出样例:
4
55
9
15

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #include <algorithm>
  5. using namespace std;
  6. typedef long long LL;
  7. const int N = 100010;
  8. int n, m;
  9. int w[N];
  10. struct Node
  11. {
  12. int l, r;
  13. LL sum, add;
  14. }tr[N * 4];
  15. void pushup(int u)
  16. {
  17. tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
  18. }
  19. void pushdown(int u)
  20. {
  21. auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
  22. if (root.add)
  23. {
  24. left.add += root.add, left.sum += (LL)(left.r - left.l + 1) * root.add;
  25. right.add += root.add, right.sum += (LL)(right.r - right.l + 1) * root.add;
  26. root.add = 0;
  27. }
  28. }
  29. void build(int u, int l, int r)
  30. {
  31. if (l == r) tr[u] = {l, r, w[r], 0};
  32. else
  33. {
  34. tr[u] = {l, r};
  35. int mid = l + r >> 1;
  36. build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
  37. pushup(u);
  38. }
  39. }
  40. void modify(int u, int l, int r, int d)
  41. {
  42. if (tr[u].l >= l && tr[u].r <= r)
  43. {
  44. tr[u].sum += (LL)(tr[u].r - tr[u].l + 1) * d;
  45. tr[u].add += d; // 这里懒标记了,等下次用到这个点的时候再更新就行
  46. // 那么什么时候用到呢
  47. /*
  48. 1. 再次修改,可能会涉及到这个区间,所以下面的else那块有一样pushdown
  49. 2. 查询时涉及这个区间,那么一定得先pushdown
  50. */
  51. }
  52. else // 一定要分裂
  53. {
  54. pushdown(u);
  55. int mid = tr[u].l + tr[u].r >> 1;
  56. if (l <= mid) modify(u << 1, l, r, d);
  57. if (r > mid) modify(u << 1 | 1, l, r, d);
  58. pushup(u);
  59. }
  60. }
  61. LL query(int u, int l, int r)
  62. {
  63. if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
  64. pushdown(u);
  65. int mid = tr[u].l + tr[u].r >> 1;
  66. LL sum = 0;
  67. if (l <= mid) sum = query(u << 1, l, r);
  68. if (r > mid) sum += query(u << 1 | 1, l, r);
  69. return sum;
  70. }
  71. int main()
  72. {
  73. scanf("%d%d", &n, &m);
  74. for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
  75. build(1, 1, n);
  76. char op[2];
  77. int l, r, d;
  78. while (m -- )
  79. {
  80. scanf("%s%d%d", op, &l, &r);
  81. if (*op == 'C')
  82. {
  83. scanf("%d", &d);
  84. modify(1, l, r, d);
  85. }
  86. else printf("%lld\n", query(1, l, r));
  87. }
  88. return 0;
  89. }

1277. 维护序列

老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。
有长为 N 的数列,不妨设为 a1,a2,…,aN。
有如下三种操作形式:
把数列中的一段数全部乘一个值;
把数列中的一段数全部加一个值;
询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 P 的值。
输入格式
第一行两个整数 N 和 P;
第二行含有 N 个非负整数,从左到右依次为 a1,a2,…,aN;
第三行有一个整数 M,表示操作总数;
从第四行开始每行描述一个操作,输入的操作有以下三种形式:
操作 1:1 t g c,表示把所有满足 t≤i≤g 的 ai 改为 ai×c;
操作 2:2 t g c,表示把所有满足 t≤i≤g 的 ai 改为 ai+c;
操作 3:3 t g,询问所有满足 t≤i≤g 的 ai 的和模 P 的值。
同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。
输出格式
对每个操作 3,按照它在输入中出现的顺序,依次输出一行一个整数表示询问结果。
输入样例:
7 43
1 2 3 4 5 6 7
5
1 2 5 5
3 2 4
2 3 7 9
3 1 3
3 4 7
输出样例:
2
35
8
样例解释
初始时数列为 {1,2,3,4,5,6,7};
经过第 1 次操作后,数列为 {1,10,15,20,25,6,7};
对第 2 次操作,和为 10+15+20=45,模 43 的结果是 2;
经过第 3 次操作后,数列为 {1,10,24,29,34,15,16};
对第 4 次操作,和为 1+10+24=35,模 43 的结果是 35;
对第 5 次操作,和为 29+34+15+16=94,模 43 的结果是 8。

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #include <algorithm>
  5. using namespace std;
  6. typedef long long LL;
  7. const int N = 100010;
  8. int n, m,p;
  9. int w[N];
  10. struct Node
  11. {
  12. int l, r;
  13. LL sum, add, mul;
  14. }tr[N * 4];
  15. void pushup(int u)
  16. {
  17. tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum)%p; // 这里取模
  18. }
  19. void eval(Node& t,LL add,LL mul){
  20. t.sum = (t.sum*mul)%p + (((LL)t.r - t.l + 1)*add) % p; // 取模
  21. t.mul = (t.mul*mul)%p;
  22. t.add = (t.add * mul + add)%p;
  23. }
  24. void pushdown(int u)
  25. {
  26. auto &t = tr[u];
  27. eval(tr[u<<1],t.add,t.mul);
  28. eval(tr[u<<1|1],t.add,t.mul);
  29. t.add = 0;
  30. t.mul = 1;
  31. }
  32. void build(int u, int l, int r)
  33. {
  34. if (l == r) tr[u] = {l, r, w[r], 0, 1 }; // 这里要改
  35. else
  36. {
  37. tr[u] = {l, r,0,0,1}; // 这里要改
  38. int mid = l + r >> 1;
  39. build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
  40. pushup(u);
  41. }
  42. }
  43. void modify(int u, int l, int r, LL add, LL mul)
  44. {
  45. if (tr[u].l >= l && tr[u].r <= r)
  46. {
  47. eval(tr[u], add, mul);
  48. }
  49. else // 一定要分裂
  50. {
  51. pushdown(u);
  52. int mid = tr[u].l + tr[u].r >> 1;
  53. if (l <= mid) modify(u << 1, l, r, add,mul);
  54. if (r > mid) modify(u << 1 | 1, l, r, add,mul);
  55. pushup(u);
  56. }
  57. }
  58. LL query(int u, int l, int r)
  59. {
  60. if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
  61. pushdown(u);
  62. int mid = tr[u].l + tr[u].r >> 1;
  63. LL sum = 0;
  64. if (l <= mid) sum = query(u << 1, l, r)%p;
  65. if (r > mid) {
  66. sum = (sum + query(u << 1 | 1, l, r))%p;
  67. }
  68. return sum;
  69. }
  70. int main()
  71. {
  72. scanf("%d%d", &n,&p);
  73. for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
  74. cin >> m;
  75. build(1, 1, n);
  76. int op;
  77. int l, r;
  78. LL d;
  79. while (m -- )
  80. {
  81. scanf("%d%d%d", &op, &l, &r);
  82. if (op == 1)
  83. {
  84. scanf("%d", &d);
  85. modify(1, l, r, 0,d);
  86. }else if(op==2){
  87. scanf("%d", &d);
  88. modify(1, l, r, d,1);
  89. }
  90. else printf("%lld\n", query(1, l, r)%p);
  91. }
  92. return 0;
  93. }

247. 亚特兰蒂斯

有几个古希腊书籍中包含了对传说中的亚特兰蒂斯岛的描述。
其中一些甚至包括岛屿部分地图。
但不幸的是,这些地图描述了亚特兰蒂斯的不同区域。
您的朋友 Bill 必须知道地图的总面积。
你自告奋勇写了一个计算这个总面积的程序。
输入格式
输入包含多组测试用例。
对于每组测试用例,第一行包含整数 n,表示总的地图数量。
接下来 n 行,描绘了每张地图,每行包含四个数字 x1,y1,x2,y2(不一定是整数),(x1,y1) 和 (x2,y2) 分别是地图的左上角位置和右下角位置。
注意,坐标轴 x 轴从上向下延伸,y 轴从左向右延伸。
当输入用例 n=0 时,表示输入终止,该用例无需处理。
输出格式
每组测试用例输出两行。
第一行输出 Test case #k,其中 k 是测试用例的编号,从 1 开始。
第二行输出 Total explored area: a,其中 a 是总地图面积(即此测试用例中所有矩形的面积并,注意如果一片区域被多个地图包含,则在计算总面积时只计算一次),精确到小数点后两位数。
在每个测试用例后输出一个空行。
输入样例:
2
10 10 20 20
15 15 25 25.5
0
输出样例:
Test case #1
Total explored area: 180.00
样例解释
样例所示地图覆盖区域如下图所示,两个矩形区域所覆盖的总面积,即为样例的解。
image.png
image.png
image.png
image.png
建立了线段树, 我们再考虑实际线段要怎么存进去, 假设我们存储的是点. 那么当前最小节点是类似[1, 1], [2, 2]这样,
显然他们单独的长度都是0(因为是点)
假设离散化后的ys为 [5.1, 6.2, 8.5, 10.8, …]
考虑线段树内[1, 1], [2, 2]的父节点[1, 2], 我们若需要[1, 2]这个节点的len能够
映射离散化区间里面6.2到8.5的距离的话, 即需要tr[x].r找到8.5, tr[x].l找到6.2 这样看着是可行的
但是这样的话, 当线段树被切分成单位点的时候, 比如若线段树根节点是[0, 2]
要查询[1,2]的长度, 按照线段树国际惯例, 我们下一步会切成[0, 1]和[2, 2], 那么可以发现, 这里我们弄断了两点之间的线段, 且很难继续记录他们之间的关联, 导致难以进行下去

所以, 我们换一种映射方法, 我们考虑令线段树的最小单位是映射相邻两点之间的线段:
同样假设离散化后的ys为 [5.1, 6.2, 8.5, 10.8, …]
考虑线段树区间[0, 0], 我们需要它映射到5.1~6.2这个区间, 同理[1, 1]映射[6.2, 8.5] …
那么, 我们每个线段树最小单位映射的长度, 就应该是ys[tr[t].r + 1] - ys[tr[t].l] 即可
很容易看出, 该做法可以推到更上层的节点也不会出错

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <vector>
  5. using namespace std;
  6. const int N = 10010;
  7. int n;
  8. // 用于存每一组数据里面的每一条竖线(线段)
  9. struct Segment
  10. {
  11. double x, y1, y2;
  12. int k; //标记左右竖线
  13. bool operator<(const Segment &t)const
  14. {
  15. return x < t.x;
  16. }
  17. }seg[N * 2]; // 每个测试用例包括n组数据(n<=10000), 每组数组是1个矩形, 存2条竖线
  18. struct Node
  19. {
  20. int l, r; // 左右端点
  21. int cnt; // 当前区段(整段)被有效覆盖次数
  22. double len; // 当前区间内的有效长度
  23. }tr [N * 8]; // 每个测试用例最多存在, 2n个y坐标再 * 4(线段树)
  24. vector<double> ys;
  25. int find(double y)
  26. {
  27. return lower_bound(ys.begin(), ys.end(), y) - ys.begin();
  28. }
  29. void pushup(int u)
  30. {
  31. // 如果该"整段"需要算入len
  32. if (tr[u].cnt) tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l];
  33. // 如果tr[u].cnt等于0其实有两种情况:
  34. // 1. 完全覆盖. 这种情况由modify的第一个if进入.
  35. // 这时下面其实等价于把"由完整的l, r段贡献给len的部分清除掉",
  36. // 而留下其他可能存在的子区间段对len的贡献
  37. // 2. 不完全覆盖, 这种情况由modify的else最后一行进入.
  38. // 表示区间并不是完全被覆盖,可能有部分被覆盖,所以要通过儿子的信息来更新
  39. else if (tr[u].cnt == 0 && tr[u].l != tr[u].r) //加个==0清晰点
  40. {
  41. tr[u].len = tr[u<<1].len + tr[u<<1|1].len;
  42. }
  43. else tr[u].len = 0; // 不可再分且cnt等于0, 那就直接安全置0
  44. }
  45. void build(int u, int l, int r)
  46. {
  47. if (l == r) tr[u] = {l, r, 0, 0};
  48. if (l != r)
  49. {
  50. tr[u] = {l, r};
  51. int mid = l + r >> 1;
  52. build(u<<1, l, mid), build(u<<1|1, mid+1, r);
  53. pushup(u);
  54. }
  55. }
  56. void modify(int u, int l, int r, int k)
  57. {
  58. // 线段被包住, 说明整体都是要改的
  59. if (tr[u].l >= l && tr[u].r <= r)
  60. {
  61. tr[u].cnt += k; // cnt是当前还在计算面积的矩形个数
  62. pushup(u);
  63. }
  64. else
  65. {
  66. int mid = tr[u].l + tr[u].r >> 1;
  67. if (l <= mid) modify(u<<1, l, r, k);
  68. if (r > mid) modify(u<<1|1, l, r, k);
  69. pushup(u);
  70. }
  71. }
  72. // debug用
  73. void print_tree()
  74. {
  75. puts("tree:");
  76. for (int i = 0; i <= ys.size()-2; i++)
  77. {
  78. printf("i: %d, l: %d r: %d cnt: %d, len: %lf\n",
  79. i, tr[i].l, tr[i].r, tr[i].cnt, tr[i].len);
  80. }
  81. }
  82. int main()
  83. {
  84. int T = 1;
  85. // 测试用例组数不定, 直到n为0才停止
  86. while (scanf("%d", &n), n)
  87. {
  88. ys.clear();
  89. // 每组测试用例有n个矩形输入
  90. for (int i = 0, j = 0; i < n; i++)
  91. {
  92. double x1, y1, x2, y2;
  93. scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
  94. seg[j++] = {x1, y1, y2, 1}; //左竖线
  95. seg[j++] = {x2, y1, y2, -1}; //右竖线
  96. ys.push_back(y1), ys.push_back(y2); // 离散化压缩空间
  97. }
  98. // 离散化
  99. sort(ys.begin(), ys.end());
  100. ys.erase(unique(ys.begin(), ys.end()), ys.end());
  101. // 对该组所有测试用例的y坐标离散后所在区间构造线段树
  102. // y轴有ys.size()个点, 而我们线段树需要存线段, 这里一共有ys.size()-1个线段
  103. // 而线段树build是每个单位点为最小单位, 所以最少得build ys.size()-1个点
  104. // 所以r最小是build(1, 0, ys.size()-2) 实际上r只需要不比这个ys.size()-2小就行
  105. build(1, 0, ys.size() - 2);
  106. // 建立了线段树, 我们再考虑实际线段要怎么存进去, 当前最小节点是类似[1, 1], [2, 2]这样,
  107. // 显然他们单独的长度都是0(因为是点)
  108. // 假设离散化后的ys为 [5.1, 6.2, 8.5, 10.8, ...]
  109. // 考虑线段树内[1, 1], [2, 2]的父节点[1, 2], 我们若需要[1, 2]这个节点的len能够
  110. // 映射离散化区间里面6.2到8.5的距离的话, 即需要tr[x].r找到8.5, tr[x].l找到6.2
  111. // 这样看着是可行的
  112. // 但是这样的话, 当线段树被切分成单位点的时候, 比如若线段树根节点是[0, 2]
  113. // 要查询[1,2]的长度, 按照线段树国际惯例, 我们下一步会切成[0, 1]和[2, 2], 那么可以发现,
  114. // 这里我们弄断了两点之间的线段, 且很难继续记录他们之间的关联, 导致难以进行下去
  115. // 所以, 我们换一种映射方法, 我们考虑令线段树的最小单位是映射相邻两点之间的线段:
  116. // 同样假设离散化后的ys为 [5.1, 6.2, 8.5, 10.8, ...]
  117. // 考虑线段树区间[0, 0], 我们需要它映射到5.1~6.2这个区间, 同理[1, 1]映射[6.2, 8.5] ...
  118. // 那么, 我们每个线段树最小单位映射的长度, 就应该是ys[tr[t].r + 1] - ys[tr[t].l] 即可
  119. // 很容易看出, 该做法可以推到更上层的节点也不会出错
  120. // 根据x的大小排序, 确定后面遍历是正确的从左到右
  121. sort(seg, seg + n * 2);
  122. double res = 0;
  123. for (int i = 0; i < n * 2; i++)
  124. {
  125. // tr[1].len是当前y轴实际使用长度, 乘上x就是面积
  126. if (i > 0)res += tr[1].len * (seg[i].x - seg[i-1].x);
  127. // 线段树插入当前y轴上的用到的线段, .k是标记左右竖线
  128. // 参考上面调用build之前的注释, 从tr去取出真实ys坐标(y轴离散化后)要+1
  129. // 那么从真实ys坐标找回对应tr内的右点就减回去1
  130. modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k);
  131. }
  132. printf("Test case #%d\n", T++);
  133. printf("Total explored area: %.2lf\n\n", res);
  134. }
  135. return 0;
  136. }

image.png
image.png

可持久化数据结构

最大异或和

给定一个非负整数序列 a,初始长度为 N。
有 M 个操作,有以下两种操作类型:
A x:添加操作,表示在序列末尾添加一个数 x,序列的长度 N 增大 1。
Q l r x:询问操作,你需要找到一个位置 p,满足 l≤p≤r,使得:a[p] xor a[p+1] xor … xor a[N] xor x 最大,输出这个最大值。
输入格式
第一行包含两个整数 N,M,含义如问题描述所示。
第二行包含 N 个非负整数,表示初始的序列 A。
接下来 M 行,每行描述一个操作,格式如题面所述。
输出格式
每个询问操作输出一个整数,表示询问的答案。
每个答案占一行。
输入样例:
5 5
2 6 4 3 6
A 1
Q 3 5 4
A 4
Q 5 7 0
Q 3 6 6
输出样例:
4
5
6
image.png

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #include <algorithm>
  5. using namespace std;
  6. const int N = 600010, M = N * 25;
  7. int n, m;
  8. int s[N];
  9. int tr[M][2], max_id[M];
  10. int root[N], idx;
  11. /*
  12. i是s[i]中的i,即s的下标
  13. k是当前处理到第几位
  14. p是上一个版本
  15. q是最新版本
  16. */
  17. void insert(int i, int k, int p, int q)
  18. {
  19. if (k < 0)
  20. {
  21. max_id[q] = i;
  22. return;
  23. }
  24. int v = s[i] >> k & 1;
  25. // 当前这位是v可以将原先的覆盖掉
  26. // 所以只需要将v^1 这一位从p这个版本复制过来
  27. if (p) tr[q][v ^ 1] = tr[p][v ^ 1];
  28. // 当前的点必须开一个新的点
  29. tr[q][v] = ++ idx;
  30. // 递归插入另一个
  31. insert(i, k - 1, tr[p][v], tr[q][v]);
  32. // 更新一下当前版本的最大id
  33. max_id[q] = max(max_id[tr[q][0]], max_id[tr[q][1]]);
  34. }
  35. /*
  36. 本题目是在区间[l,r]中寻找一个p使得
  37. a[p]^a[p+1].....^a[N] ^ x值最大
  38. 那考虑到b^a^a = b
  39. 所以我们预处理一个前缀和
  40. s[1] = a[1]
  41. s[2] = a[1]^a[2]
  42. s[3] = a[1]^a[2]^a[3]
  43. ....
  44. s[n] = a[1]^a[2]^a[3]....^a[n]
  45. 要 求a[p]^a[p+1].....^a[N]的值,只需要s[n]^s[p-1]即可
  46. */
  47. /*
  48. root 根节点
  49. C 从root为根的子树中找与C异或最大的数
  50. 因此题目中的p是从[l,r]中找
  51. 那么p-1就是从[l-1,r-1]中找
  52. */
  53. int query(int root, int C, int L)
  54. {
  55. int p = root;
  56. for (int i = 23; i >= 0; i -- )
  57. {
  58. int v = C >> i & 1;
  59. if (max_id[tr[p][v ^ 1]] >= L) p = tr[p][v ^ 1];
  60. else p = tr[p][v];
  61. }
  62. return C ^ s[max_id[p]];
  63. }
  64. int main()
  65. {
  66. scanf("%d%d", &n, &m);
  67. /*
  68. 这里为什么必须将max_id[0]设置成一个非法值?
  69. 注意到在查询函数中
  70. if (max_id[tr[p][v ^ 1]] >= L) p = tr[p][v ^ 1];
  71. 如果tr[p][v^1]这个点不存在,那么他的值肯定是0,
  72. 那就相当于
  73. if (max_id[0] >= L) p = tr[p][v ^ 1];
  74. 当L = 0时,本来是不存在这个点(tr[p][v ^ 1])的却要走向这个点,那么走向这个点
  75. 就一定会将结果变小
  76. 假设前面已经匹配一段了abcde,待匹配是xxxx
  77. abcdexxxx
  78. 但是如果走向这个不存在的点的话query函数最后返回那块
  79. C ^ s[max_id[p]];
  80. s[max_id[p]] = s[0]=0,
  81. 而正确结果至少是匹配abcde这一段大于等于0
  82. */
  83. max_id[0] = -1;
  84. /*
  85. 为什么又要初始化root[0]并且插入0节点呢
  86. 因为p-1的范围是[l-1,r-1],当l-1==0时,意味着有可能和0匹配,所以必须加
  87. */
  88. root[0] = ++ idx;
  89. insert(0, 23, 0, root[0]);
  90. for (int i = 1; i <= n; i ++ )
  91. {
  92. int x;
  93. scanf("%d", &x);
  94. s[i] = s[i - 1] ^ x;
  95. root[i] = ++ idx;
  96. insert(i, 23, root[i - 1], root[i]);
  97. }
  98. char op[2];
  99. int l, r, x;
  100. while (m -- )
  101. {
  102. scanf("%s", op);
  103. if (*op == 'A')
  104. {
  105. scanf("%d", &x);
  106. n ++ ;
  107. s[n] = s[n - 1] ^ x;
  108. root[n] = ++ idx;
  109. insert(n, 23, root[n - 1], root[n]);
  110. }
  111. else
  112. {
  113. scanf("%d%d%d", &l, &r, &x);
  114. printf("%d\n", query(root[r - 1], s[n] ^ x, l - 1));
  115. }
  116. }
  117. return 0;
  118. }
  1. #include <cstdio>
  2. #include <cstring>
  3. #include <algorithm>
  4. using namespace std;
  5. // 30w初始数据和30w新增, 而10的7次方小于2的24次方, 再加上根节点, 就是说每个数最多需要25位;
  6. const int N = 600010, M = N * 25;
  7. int n, m;
  8. int s[N]; // 前缀和序列
  9. int tr[M][2];
  10. int max_id[M]; // 用于记录当前根节点版本的最大id范围
  11. int root[N], idx;
  12. // i是第i个插入的数的i, p是上一个插入的数的节点号, q是当前节点号, k是现在取到第k位
  13. void insert(int i, int k, int p, int q)
  14. {
  15. // 如果记录结束了
  16. if (k < 0)
  17. {
  18. max_id[q] = i; // 记录当前节点(可能会被后面公用)所能到达的最大范围i
  19. return;
  20. }
  21. // 取出前缀和的二进制表示从右往左数第k位v
  22. // 需要注意的是, 这个s[i]就是我们要存的东西!!!!!
  23. int v = s[i] >> k & 1;
  24. // 如果前一个节点存在当前节点没有的分支, 那就把当前节点的这个空的路径指过去, 这就相当于复制!
  25. if (p) tr[q][v ^ 1] = tr[p][v ^ 1];
  26. tr[q][v] = ++idx; // 现在才是正常trie树插入
  27. // 递归插入下一位二进制, tr[q][v]就是我们本轮插入的新节点
  28. // 而前面我们只复制了前一轮的不同v方向的路径, v方向的还没动过, 于是放到p里面等下一轮
  29. // 至于为什么可以放到下一轮, 因为当前q新插入的数字(二进制当前位)是v, 而p的这条路径也是v
  30. // 所以暂时不需要复制
  31. insert(i, k - 1, tr[p][v], tr[q][v]);
  32. // 下面是递归到所有点都插入完成才开始进行的, 所以能把最大max_id递归传递回去
  33. // 每个点的最大范围用子节点最大的值, 然后还能递归传递回去, 因为当前递归层
  34. // 的q, 就是上一层的tr[q][v], 观察易知每个节点都会有对应max_id
  35. max_id[q] = max(max_id[tr[q][0]], max_id[tr[q][1]]);
  36. }
  37. int query(int r, int C, int l)
  38. {
  39. // 选用合适的root, 就是第r-1个节点作为root(-1已在传参前完成)
  40. // 然后根据异或的前缀和性质才能保证在r左边
  41. int p = root[r];
  42. for (int i = 23; i >= 0; i--)
  43. {
  44. // C是s[n] ^ x, 从高位到低位逐位检索二进制每一位上能跟C异或结果最大的数
  45. int v = C >> i & 1;
  46. // 自带判空功能如果没有点, max_id会为0, 那就肯定不能满足>=l
  47. // 而max_id又同时可以限制当前的点是在l r区间内
  48. // 另外, 如果tr[p][v^1]为空, 那么tr[p][v]就肯定不为空,并在l r区间, 因为根据
  49. // 插入的代码, 每个节点至少有一条当前s[i]的完整路径
  50. // 而如果tr[p][v^1]不为空但maxid小于l, 同理也能选取到tr[p][v]
  51. if (max_id[tr[p][v ^ 1]] >= l) p = tr[p][v ^ 1];
  52. else p = tr[p][v];
  53. }
  54. return C ^ s[max_id[p]];
  55. }
  56. int main()
  57. {
  58. scanf("%d%d", &n, &m);
  59. // 前缀和, 初始化root[0]
  60. max_id[0] = -1;
  61. root[0] = ++idx;
  62. insert(0, 23, 0, root[0]);
  63. for (int i = 1; i <= n; i++)
  64. {
  65. int x;
  66. scanf("%d", &x);
  67. s[i] = s[i - 1] ^ x; // 前缀和序列
  68. root[i] = ++idx;
  69. insert(i, 23, root[i - 1], root[i]);
  70. }
  71. char op[2];
  72. int l, r, x;
  73. while (m--)
  74. {
  75. scanf("%s", op);
  76. if (op[0] == 'A')
  77. {
  78. scanf("%d", &x);
  79. n++;
  80. s[n] = s[n - 1] ^ x;
  81. root[n] = ++idx;
  82. insert(n, 23, root[n - 1], root[n]);
  83. }
  84. else
  85. {
  86. scanf("%d%d%d", &l, &r, &x);
  87. // 至少要包住第r个点, 所以用r-1, 否则会因为异或把root[r]抵消掉
  88. // l也同理
  89. printf("%d\n", query(r - 1, s[n] ^ x, l - 1));
  90. }
  91. }
  92. return 0;
  93. }
  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. using namespace std;
  5. const int N = 6e5+10,M = N*24;
  6. int tr[M][2],idx,maxid[M],root[N];
  7. int s[N];
  8. int n,m;
  9. /*
  10. 下面insert函数中注意
  11. if(lp){
  12. tr[p][u^1] = tr[lp][u^1];
  13. lp = tr[lp][u];
  14. }
  15. 这段是非常巧妙的,试问自己一个问题,这棵trie树是如何保存到历史版本的
  16. 关键就在于 if函数里面那两句
  17. 理解一下: 这里的tr其实是一个指针,直接将现在的节点指向了,上一个版本的trie树中的节点
  18. 而且还能保证不重不漏
  19. */
  20. void insert(int k,int lp,int p,int x){
  21. maxid[p] = k;
  22. for (int i = 23; i >=0; i -- ){
  23. int u = x>>i&1;
  24. if(lp){
  25. tr[p][u^1] = tr[lp][u^1];
  26. lp = tr[lp][u];
  27. }
  28. tr[p][u] = ++idx;
  29. p = tr[p][u];
  30. maxid[p]=k;
  31. }
  32. }
  33. int query(int p,int x,int l){
  34. for (int i = 23; i >=0; i -- ){
  35. int u = x>>i&1;
  36. if(maxid[tr[p][u^1]]>=l){
  37. p = tr[p][u^1];
  38. }else{
  39. p = tr[p][u];
  40. }
  41. }
  42. return x^s[maxid[p]];
  43. }
  44. int main()
  45. {
  46. cin >> n >> m;
  47. maxid[0]=-1;
  48. root[0]=++idx;
  49. insert(0,root[0],root[0],0);
  50. for (int i = 1; i <= n; i ++ ){
  51. int x ;
  52. cin >> x ;
  53. s[i] = s[i-1]^x;
  54. root[i] = ++idx;
  55. insert(i,root[i-1],root[i],s[i]);
  56. }
  57. char op[2];
  58. int l,r,x;
  59. while (m -- ){
  60. scanf("%s", &op);
  61. if(op[0]=='A'){
  62. cin >> x;
  63. n++;
  64. root[n] = ++idx;
  65. s[n] = s[n-1]^x;
  66. insert(n,root[n-1],root[n],s[n]);
  67. }else{
  68. cin >> l >> r >> x;
  69. cout << query(root[r-1],s[n]^x,l-1) <<endl;
  70. }
  71. }
  72. }

255. 第K小数

给定长度为 N 的整数序列 A,下标为 1∼N。
现在要执行 M 次操作,其中第 i 次操作为给出三个整数 li,ri,ki,求 A[li],A[li+1],…,A[ri] (即 A 的下标区间 [li,ri])中第 ki 小的数是多少。
输入格式
第一行包含两个整数 N 和 M。
第二行包含 N 个整数,表示整数序列 A。
接下来 M 行,每行包含三个整数 li,ri,ki,用以描述第 i 次操作。
输出格式
对于每次操作输出一个结果,表示在该次操作中,第 k 小的数的数值。
每个结果占一行。
输入样例:
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
输出样例:
5
6
3

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <vector>
  5. using namespace std;
  6. const int N = 1e5 + 10;
  7. int n, m;
  8. int a[N];
  9. vector<int> nums; // 用于离散化
  10. struct Node {
  11. int l, r, cnt; // l和r分别是左儿子和右儿子的编号,cnt是在某个区间内元素个数
  12. // 而这个区间是默认的
  13. } tr[N * 4 + 17 * N];
  14. int root[N], idx;
  15. int find(int x) {
  16. return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
  17. }
  18. int insert(int p, int l, int r, int x) {
  19. int q = ++idx;
  20. tr[q] = tr[p];
  21. if (l == r) {
  22. tr[q].cnt++;
  23. return q;
  24. }
  25. int mid = l + r >> 1;
  26. if (x <= mid) tr[q].l = insert(tr[p].l, l, mid, x);
  27. else tr[q].r = insert(tr[p].r, mid + 1, r, x);
  28. tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
  29. return q;
  30. }
  31. int query(int q, int p, int l, int r, int k) {
  32. if (l == r) return r;
  33. int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
  34. int mid = l + r >> 1;
  35. if (k <= cnt) return query(tr[q].l, tr[p].l, l, mid, k);
  36. else return query(tr[q].r, tr[p].r, mid + 1, r, k - cnt);
  37. }
  38. int main() {
  39. cin >> n >> m;
  40. for (int i = 1; i <= n; i++) {
  41. cin >> a[i];
  42. nums.push_back(a[i]);
  43. }
  44. // 排序,去重
  45. sort(nums.begin(), nums.end());
  46. nums.erase(unique(nums.begin(), nums.end()), nums.end());
  47. // 插入各个元素
  48. for (int i = 1; i <= n; i++)
  49. root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[i]));
  50. while (m--) {
  51. int l, r, k;
  52. scanf("%d%d%d", &l, &r, &k);
  53. cout << nums[query(root[r], root[l - 1], 0, nums.size() - 1, k)];
  54. }
  55. }

AC自动机

1282. 搜索关键词

给定 n 个长度不超过 50 的由小写英文字母组成的单词,以及一篇长为 m 的文章。
请问,其中有多少个单词在文章中出现了。
注意:每个单词不论在文章中出现多少次,仅累计 1 次。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
对于每组数据,第一行一个整数 n,接下去 n 行表示 n 个单词,最后一行输入一个字符串,表示文章。
出格式
对于每组数据,输出一个占一行的整数,表示有多少个单词在文章中出现。
数据范围
1≤n≤10^4,
1≤m≤10^6
输入样例:
1
5
she
he
say
shr
her
yasherhs
输出样例:
3

这道题跟平常的ac自动机有点不一样之处 就是 cnt[p]统计以p为后缀的所有单词的时候,在build else { ne[p] = tr[ne[t]][i]; q[++tt] = p; } 这块时不能直接写上 cnt[p]+=cnt[ne[p]],可能你会想到一种办法,那就是在主函数遍历那块写成 int j = str[i]-‘a’; p = tr[p][j]; // 走到了ac自动机的这个节点上 res += cnt[p]; int u = p; while(u){ cnt[u]=0; u = ne[u]; } 这样就因为cnt[p]包含了cnt[ne[p]], 所以要把它所有的ne[p]的cnt清零

但是,但是,但是,这也是不对的!!!

  1. 因为有可能有一个更长的字符串也包含了ne[p],那么这个字符串也应该减去ne[p],但是很显然减不掉

所以只能用下面这种解法

  1. 不能将cnt的计算放到build里面 ```cpp

    include

    include

    include

using namespace std;

const int N = 1e4 + 10, M = N * 55; int tr[M][26], idx; int cnt[M], ne[M]; int q[M];

void insert(string str) // 将str插入Trie中 { int p = 0; for (int i = 0; i < str.size(); i++) { int u = str[i] - ‘a’; if (!tr[p][u]) tr[p][u] = ++idx; p = tr[p][u]; } cnt[p]++; // 记录单词出现次数 }

void build() // 创建AC自动机 { int hh = 0, tt = -1; for (int i = 0; i < 26; i++) if (tr[0][i]) q[++tt] = tr[0][i]; while (hh <= tt) { int t = q[hh++]; for (int i = 0; i < 26; i++) { int p = tr[t][i]; if (!p) tr[t][i] = tr[ne[t]][i]; else { ne[p] = tr[ne[t]][i]; q[++tt] = p; } } } }

int main() { int T; cin >> T; while (T—) { memset(cnt, 0, sizeof cnt); memset(ne, 0, sizeof ne); memset(tr, 0, sizeof tr); idx = 0; int n; cin >> n; for (int i = 0; i < n; i++) { string s; cin >> s; insert(s); } build(); string str; cin >> str; // 看看文章中出现多少单词 int p = 0, res = 0; for (int i = 0; i < str.size(); i++) { // 枚举每个字母 int j = str[i] - ‘a’; p = tr[p][j]; // 走到了ac自动机的这个节点上 int q = p; while (q) { res += cnt[q]; cnt[q] = 0; q = ne[q]; } } cout << res << endl; }

}

  1. <a name="RgTog"></a>
  2. ### 1285. 单词
  3. 某人读论文,一篇论文是由许多单词组成的。<br />但他发现一个单词会在论文中出现很多次,现在他想知道每个单词分别在论文中出现多少次。<br />**输入格式**<br />第一行一个整数 N,表示有多少个单词。<br />接下来 N 行每行一个单词,单词中只包含小写字母。<br />**输出格式**<br />输出 N 个整数,每个整数占一行,第 i 行的数字表示第 i 个单词在文章中出现了多少次。<br />**数据范围**<br />1≤N≤200,<br />所有单词长度的总和不超过 106。<br />**输入样例:**<br />3<br />a<br />aa<br />aaa<br />**输出样例:**<br />6<br />3<br />1<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/1788465/1646893207351-1703df44-0032-476a-8fdf-0ac7ad1f4b86.png#clientId=uecc76e2e-0968-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=996&id=u73ce314d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1556&originWidth=1096&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1848423&status=done&style=none&taskId=u12bd6fa8-c571-41b5-b4e8-252506a56b9&title=&width=701.44)
  4. ```cpp
  5. #include <cstdio>
  6. #include <cstring>
  7. #include <iostream>
  8. #include <algorithm>
  9. using namespace std;
  10. const int N = 1000010;
  11. int n;
  12. int tr[N][26], f[N], idx;
  13. int q[N], ne[N];
  14. char str[N];
  15. int id[210];
  16. void insert(int x)
  17. {
  18. int p = 0;
  19. for (int i = 0; str[i]; i ++ )
  20. {
  21. int t = str[i] - 'a';
  22. if (!tr[p][t]) tr[p][t] = ++ idx;
  23. p = tr[p][t];
  24. f[p] ++ ;
  25. }
  26. id[x] = p;
  27. }
  28. void build()
  29. {
  30. int hh = 0, tt = -1;
  31. for (int i = 0; i < 26; i ++ )
  32. if (tr[0][i])
  33. q[ ++ tt] = tr[0][i];
  34. while (hh <= tt)
  35. {
  36. int t = q[hh ++ ];
  37. for (int i = 0; i < 26; i ++ )
  38. {
  39. int &p = tr[t][i];
  40. if (!p) p = tr[ne[t]][i];
  41. else
  42. {
  43. ne[p] = tr[ne[t]][i];
  44. q[ ++ tt] = p;
  45. }
  46. }
  47. }
  48. }
  49. int main()
  50. {
  51. scanf("%d", &n);
  52. for (int i = 0; i < n; i ++ )
  53. {
  54. scanf("%s", str);
  55. insert(i);
  56. }
  57. build();
  58. for (int i = idx - 1; i >= 0; i -- ) f[ne[q[i]]] += f[q[i]];
  59. for (int i = 0; i < n; i ++ ) printf("%d\n", f[id[i]]);
  60. return 0;
  61. }