一般線段樹與權值線段樹
目錄
一般線段樹與權值線段樹
1.演算法分析
- 一般還要開4N的陣列
- 一般做單點修改、區間查詢,加上懶標記後,可以做區間修改、區間查詢
1.1 一般線段樹
可以處理:區間加、區間乘、區間max/min、區間覆蓋等問題
1.2 權值線段樹
- 維護全域性的值域資訊,每個節點記錄的是該值域的值出現的總次數。
- 使用二分的思想(離散化的時候,需要用到)
- 支援查詢全域性K小值,全域性rank,前驅,後繼等。
- 單詞操作時間複雜度為O(logn)
- 空間複雜度為O(n)
- 相對於平衡樹的優勢:程式碼簡單,速度快
- 劣勢:值域較大時,我們需要離散化,變成離線資料結構
2.板子
2.1 線段樹入門
2.1.1 單點修改+區間查詢
// 該板子是求區間和 #include <bits/stdc++.h> using namespace std; typedef long long LL; int const N = 5e5 + 10; LL dat[N << 2]; // 4倍空間 int n, m, a[N]; // 上傳操作 void pushup(int rt) { dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; } // 建樹 void build (int rt, int l, int r) { if (l == r) { // 如果當前到達葉節點,那麼賦值 dat[rt] = a[l]; // 賦值是a[l],表示那個葉節點 return ; } int mid = (l + r) >> 1; // 遞迴建立左右子樹 build(rt << 1, l, mid); build(rt << 1 | 1, mid + 1, r); // 上傳 pushup(rt); } // 單點修改 void modify (int rt, int l, int r, int x, int y) { if (l == x && r == x) { // 遞迴到葉節點且葉節點剛好為x節點 dat[rt] += y; // 修改 return; } int mid = (l + r) >> 1; if (x <= mid) modify(rt << 1, l, mid, x, y); // 如果在左子樹 else modify(rt << 1 | 1, mid + 1, r, x, y); // 不在左子樹,比在右子樹 pushup(rt); // 上傳 } // 區間查詢 LL query(int rt, int l, int r, int L, int R) { if (L <= l && r <= R) return dat[rt]; // 如果當前rt管轄的點能夠被[L, R]完全包含,返回 int mid = (l + r) >> 1; LL res = 0; if (L <= mid) res += query(rt << 1, l, mid, L, R); // 如果和左子樹有關 if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R); // 如果可能和右子樹有關 return res; } int main() { cin >> n >> m; for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); build(1, 1, n); // 建樹 for (int i = 1, op, x, y; i <= m; ++i) { scanf("%d%d%d", &op, &x, &y); if (op == 1) modify(1, 1, n, x, y); // 單點修改a[x]+=y else printf("%lld\n", query(1, 1, n, x, y)); // 區間查詢,求[x, y]的區間和 } return 0; }
2.1.2 區間修改+區間查詢
// 該板子是求區間和 #include <bits/stdc++.h> using namespace std; typedef long long LL; int const N = 5e5 + 10; LL dat[N << 2], lazy[N << 2]; int a[N], n, m; // 上傳標記,每次左右子樹建樹/區間修改完都需要上傳 void pushup(int rt) { dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; } // 建樹 void build(int rt, int l, int r) { if (l == r) { // 遞迴到葉節點 dat[rt] = a[l]; lazy[rt] = 0; return; } // 遞迴建立左右子樹 int mid = (l + r) >> 1; build(rt << 1, l, mid); build(rt << 1 | 1, mid + 1, r); pushup(rt); // 上傳 } // 下傳,下傳標記,同時改變dat陣列 void pushdown(int rt, int l, int r) { if (lazy[rt]) { // 如果有標記 int mid = (l + r) >> 1; // 把標記給左右子樹 lazy[rt << 1] += lazy[rt]; lazy[rt << 1 | 1] += lazy[rt]; // 改變dat dat[rt << 1] += (mid - l + 1) * lazy[rt]; dat[rt << 1 | 1] += (r - mid) * lazy[rt]; // rt標記清空 lazy[rt] = 0; } return; } // 區間修改: [L, R] += x void modify(int rt, int l, int r, int L, int R, int x) { if (L <= l && r <= R) { // 如果當前區間被完全包含 dat[rt] += (r - l + 1) * x; // 修改當前區間的dat值 lazy[rt] += x; // 改變懶標記 return ; } pushdown(rt, l, r); // 下傳 // 遞迴左右子樹修改區間 int mid = (l + r) >> 1; if (L <= mid) modify(rt << 1, l, mid, L, R, x); if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, x); pushup(rt); // 上傳 return; } // 區間查詢:獲得[L, R]的區間和 LL query(int rt, int l, int r, int L, int R) { if (L <= l && r <= R) return dat[rt]; // 如果[l, r]被完全包含於[L, R] pushdown(rt, l, r); // 標記下傳 // 遞迴加上左右子樹 int mid = (l + r) >> 1; LL res = 0; if (L <= mid) res += query(rt << 1, l, mid, L, R); if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R); return res; } int main() { cin >> n >> m; for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); // 讀入陣列 build(1, 1, n); // 建樹 for (int i = 1, a, b, x, op; i <= m; ++i) { scanf("%d", &op); if (op == 1) { scanf("%d%d%d", &a, &b, &x); modify(1, 1, n, a, b, x); // 區間修改, [a, b] += x } else { scanf("%d%d", &a, &b); printf("%lld\n", query(1, 1, n, a, b)); // 區間查詢,查詢[a, b]的區間和 } } return 0; }
2.1.3 區間加乘操作
// 加乘模板
// x點原來的乘、加法標記為:mul1、add1,後來要加上的乘、加法標記為:mul2、add2
// 可以證明先乘後加最優方法
// x的值變為: x.dat => (x.dat * mul2) + (x.r - x.l + 1) * add2;
// x的乘法標記變為: x.mul1 => x.mul1 * mul2
// x的加法標記變為: x.add1 => x.add1 * mul2 + add2
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const N = 1e5 + 10;
LL dat[N << 2], mul[N << 2], add[N << 2];
int n, p, a[N], m;
// 上傳,根的值為左子樹的值和右子樹的值之和
void pushup(int rt) {
dat[rt] = (dat[rt << 1] + dat[rt << 1 | 1]) % p;
}
// 建樹
void build(int rt, int l, int r) {
if (l == r) { // 如果是葉子
dat[rt] = a[l] % p;
add[rt] = 0;
mul[rt] = 1;
return;
}
// 如果不是葉子,那麼乘法標記必須為1,加法標記為0
mul[rt] = 1;
add[rt] = 0;
int mid = (l + r) >> 1;
build(rt << 1, l, mid), build(rt << 1 | 1, mid + 1, r); // 建立左子樹和右子樹
pushup(rt);
}
// 加成的結果
void eval(int rt, int l, int r, LL add2, LL mul2) {
dat[rt] = ((dat[rt] * mul2 % p) + ((r - l + 1) % p) * add2 % p) % p;
mul[rt] = mul[rt] * mul2 % p;
add[rt] = (add[rt] * mul2 % p + add2) % p;
}
// 標記下移
void pushdown(int rt, int l, int r) {
int mid = (l + r) >> 1;
eval(rt << 1, l, mid, add[rt], mul[rt]), eval(rt << 1 | 1, mid + 1, r, add[rt], mul[rt]); // 左右子樹分別得到根的標記
add[rt] = 0, mul[rt] = 1; // 清空根的標記
return;
}
// 區間修改
void modify(int rt, int l, int r, int L, int R, LL add2, LL mul2) {
if (L <= l && r <= R) { // 如果[L, R]在[l, r]內,直接修改
eval(rt, l, r, add2, mul2);
return;
}
pushdown(rt, l, r); // 如果不在[l, r]內,那麼分裂,首先要把標記下移
int mid = (l + r) >> 1;
if (L <= mid) modify(rt << 1, l, mid, L, R, add2, mul2); // 修改左子樹
if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, add2, mul2); // 修改右子樹
pushup(rt); // 修改完子樹需要把標記上移
return;
}
// 詢問區間和[L, R]
LL query(int rt, int l, int r, int L, int R) {
if (L <= l && r <= R) return dat[rt] % p;
pushdown(rt, l, r); // 如果[L, R]不在[l, r]內,那麼需要分裂,首先要把標記下移
LL res = 0;
int mid = (l + r) >> 1;
if (L <= mid) res = query(rt << 1, l, mid, L, R) % p; // 左子樹
if (mid < R) res = (res + query(rt << 1 | 1, mid + 1, r, L, R) % p) % p; // 右子樹
return res;
}
int main() {
cin >> n >> p; // 輸入數字的個數和模數
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); // 輸入數字
build(1, 1, n); // 建樹
cin >> m; // 輸入運算元
for (int i = 1, op, t, g, c; i <= m; ++i) { // 輸入每次的具體操作
scanf("%d", &op);
if (op == 1) { // 區間乘
scanf("%d%d%d", &t, &g, &c);
modify(1, 1, n, t, g, 0, c);
}
else if (op == 2) { // 區間加
scanf("%d%d%d", &t, &g, &c);
modify(1, 1, n, t, g, c, 1);
}
else { // 詢問區間和
scanf("%d%d", &t, &g);
cout << query(1, 1, n, t, g) << endl;
}
}
return 0;
}
2.1.4 區間染色
/*本題是區間覆蓋問題,求指定區間內有多少的顏色數目,因為顏色的數目比較少,因此
可以使用一個int整數來表示所有的顏色數目,而後就是線段樹的常規操作*/
#include <bits/stdc++.h>
using namespace std;
int const N = 1e5 + 10;
typedef long long LL;
LL add[N << 2], sum[N << 2]; // add為記錄顏色的懶標記,sum為當前區間的顏色
// 向下傳遞操作
void pushup(int u) {
sum[u] = sum[u << 1] | sum[u << 1 | 1]; // 當前顏色由子區間顏色得到
}
// 向上傳遞操作
void pushdown(int u) {
if (add[u]) { // 如果當前u節點有顏色的話
// 給左右子節點標記都賦值
add[u << 1] = add[u];
add[u << 1 | 1] = add[u];
// 給左右節點的sum賦值,記錄顏色
sum[u << 1] = add[u];
sum[u << 1 | 1] = add[u];
// 去掉懶標記
add[u] = 0;
}
}
// 建樹
void build(int u, int l, int r) {
add[u] = 0; // 初始每個節點都沒有懶標記
if (l == r) { // 如果遞迴到葉節點
sum[u] = 1; // 葉節點的顏色賦值
return;
}
int mid = l + r >> 1;
build(u << 1, l, mid); // 建立左右子樹
build(u << 1| 1, mid + 1, r);
pushup(u); // 標記上傳
}
// 區間賦值操作
void modify(int u, int l, int r, int c, int L, int R) {
if (L <= l && r <= R) { // 如果[l, r]完全被包含在要賦值的區間[L, R]的話,那麼直接修改
add[u] = 1 << (c - 1);
sum[u] = 1 << (c - 1);
return;
}
pushdown(u); // 下傳標記,因為因為標記要分裂
int mid = l + r >> 1;
if (L <= mid) modify(u << 1, l, mid, c, L, R); // 遞迴修改左右子樹
if (mid < R) modify(u << 1 | 1, mid + 1, r, c, L, R);
pushup(u); // 上傳操作
}
// 區間查詢多少個顏色
LL query(int u, int l, int r, int L, int R) {
if (L <= l && r <= R) return sum[u]; // 如果[l, r]完全被包含在要賦值的區間[L, R]的話,那麼返回
pushdown(u); // 標記下移
int mid = l + r >> 1;
LL res = 0;
// 遞迴查詢左右子樹
if (L <= mid) res |= query(u << 1, l, mid, L, R);
if (mid < R) res |= query(u << 1 | 1, mid + 1, r, L, R);
return res;
}
int main() {
int L, T, O, a, b, c;
cin >> L >> T >> O; // 讀入節點數、顏色總數、運算元
build(1, 1, L);
while (O--) {
char op[2];
scanf("%s", op); // 讀入操作型別
if (op[0] == 'P') {
scanf("%d %d", &a, &b);
if (a > b) swap(a, b); // 保證a要比b小
LL ans = query(1, 1, L, a, b); // 查詢a到b的顏色總數,顏色總數用一個int型數表示
LL res = 0;
while (ans) { // 記錄這個int型數有多少個1
if (ans & 1) res++;
ans >>= 1;
}
printf("%lld\n", res);
}
else {
scanf("%d%d%d", &a, &b, &c); // 讀入[a, b]和修改為的值
if (a > b) swap(a, b);
modify(1, 1, L, c, a, b); // 修改操作
}
}
return 0;
}
2.2 權值線段樹
2.2.1 求第k大、前驅、後繼等
/*
本題由於一開始dat維護的全為0,所以不需要建樹的操作。dat維護每個數出現的次數
資料較大,需要先離散化,然後在每個離散化後的數字上建立線段樹維護每個數出現的次數。
1. 插入數值x:x的次數加一
2. 刪除數值x(若有多個相同的數,應只刪除一個):x的次數減一
3. 查詢數值x的排名(若有多個相同的數,應輸出最小的排名):區間查詢[l, x - 1]的次數,然後加一
4. 查詢排名為x的數值:看x是否小於等於左子樹的次數,如果小於在左子樹;否則就算右子樹的k-左子樹次數
5. 求數值x的前驅(前驅定義為小於x的最大的數):求出x的排名t,然後查詢排名為t-1的數
6. 求數值x的後繼(後繼定義為大於x的最小的數):求出x的排名t,然後查詢排名為t+1的數
*/
#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int num[N];
struct A{
int opt, x;
}q[N];
int dat[N << 2];
void pushup(int rt){
dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
}
// 單點修改
void modify(int rt, int l, int r, int p, int c){
if(l == r){
dat[rt] += c;
return;
}
int mid = (l + r) >> 1;
if(p <= mid) modify(rt << 1, l, mid, p, c);
else modify(rt << 1 | 1, mid + 1, r, p, c);
pushup(rt);
}
// 區間查詢
int query1(int rt, int l, int r, int L, int R){//區間求和
if (L <= l && r <= R) return dat[rt];
int mid = (l + r) >> 1;
int res = 0;
if(L <= mid) res += query1(rt << 1, l, mid, L, R);
if (mid < R) res += query1(rt << 1 | 1, mid + 1, r, L, R);
return res;
}
// 查詢排名為k的數
int query2(int rt, int l, int r, int k)
{
if(l == r) return l;
int mid = (l + r) >> 1;
if(k <= dat[rt << 1]) return query2(rt << 1, l, mid, k);
else return query2(rt << 1 | 1, mid + 1, r, k-dat[rt<<1]);
}
int main(){
int m, k=0;
scanf("%d", &m);
for(int i = 0; i < m; i++){
scanf("%d%d", &q[i].opt, &q[i].x);
if(q[i].opt != 4) num[k++] = q[i].x;
}
sort(num, num+k);
int n = unique(num, num+k) - num;
for(int i = 0; i < m; i++){
int x = lower_bound(num, num+n, q[i].x) - num + 1;
if(q[i].opt == 1){//插入
modify(1, 1, n, x, 1);
}
if(q[i].opt == 2){//刪除
modify(1, 1, n, x, -1);
}
if(q[i].opt == 3){//查詢x的排名
if(x - 1 == 0) printf("1\n");
else printf("%d\n", query1(1, 1, n, 1, x - 1) + 1);
}
if(q[i].opt == 4){//查詢排名為x的數
printf("%d\n", num[query2(1, 1, n, q[i].x) - 1]);
}
if(q[i].opt == 5){//求小於x的最大的數的值
int rk = query1(1, 1, n, 1, x - 1);
printf("%d\n", num[query2(1, 1, n, rk) - 1]);
}
if(q[i].opt == 6){//求大於x的最小的數的值
int sum = query1(1, 1, n, 1, x);
printf("%d\n", num[query2(1, 1, n, sum + 1) - 1]);
}
}
return 0;
}
3. 例題
3.1 線段樹入門
luogu P1047 校門外的樹
題意: 有一個數軸,長度為l+1,從0~l上每個點都種樹。現在有m個操作,每個操作輸入a和b,表示要把[a, b]上的樹砍掉,問m次操作後,數軸上還剩下多少棵樹?
題解: 只需要改區間修改+區間查詢的板子即可,當砍掉[a,b]上的樹時,就算把[a, b]賦值為0,最後統計還剩多少棵樹,就算計算[1,n]的區間求和。
程式碼:
// 該板子是求區間和
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const N = 5e5 + 10;
LL dat[N << 2], lazy[N << 2];
int a[N], n, m;
// 上傳標記,每次左右子樹建樹/區間修改完都需要上傳
void pushup(int rt) {
dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
}
// 建樹
void build(int rt, int l, int r) {
if (l == r) { // 遞迴到葉節點
dat[rt] = a[l];
lazy[rt] = 1;
return;
}
lazy[rt] = 1;
// 遞迴建立左右子樹
int mid = (l + r) >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
pushup(rt); // 上傳
}
// 下傳,下傳標記,同時改變dat陣列
void pushdown(int rt, int l, int r) {
if (lazy[rt] == 0) { // 如果有標記
// 把標記給左右子樹
lazy[rt << 1] = 0;
lazy[rt << 1 | 1] = 0;
// 改變dat
dat[rt << 1] = 0 ;
dat[rt << 1 | 1] = 0;
// rt標記清空
lazy[rt] = 1;
}
return;
}
// 區間修改: [L, R] += x
void modify(int rt, int l, int r, int L, int R) {
if (L <= l && r <= R) { // 如果當前區間被完全包含
dat[rt] = 0; // 修改當前區間的dat值
lazy[rt] = 0; // 改變懶標記
return ;
}
if (lazy[rt] == 0) pushdown(rt, l, r); // 下傳
// 遞迴左右子樹修改區間
int mid = (l + r) >> 1;
if (L <= mid) modify(rt << 1, l, mid, L, R);
if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R);
pushup(rt); // 上傳
return;
}
// 區間查詢:獲得[L, R]的區間和
LL query(int rt, int l, int r, int L, int R) {
if (L <= l && r <= R) return dat[rt]; // 如果[l, r]被完全包含於[L, R]
if (lazy[rt] == 0) pushdown(rt, l, r); // 標記下傳
// 遞迴加上左右子樹
int mid = (l + r) >> 1;
LL res = 0;
if (L <= mid) res += query(rt << 1, l, mid, L, R);
if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);
return res;
}
int main() {
cin >> n >> m;
n ++;
for (int i = 1; i <= n; ++i) a[i] = 1;
build(1, 1, n); // 建樹
// cout << query(1, 1, n, 1, n) << endl;
for (int i = 1, a, b; i <= m; ++i) {
scanf("%d%d", &a, &b);
a++, b++;
modify(1, 1, n, a, b);
}
cout << query(1, 1, n, 1, n) << endl;
return 0;
}
luogu P5057 [CQOI2006]簡單題
題意: 有一個 n 個元素的陣列,每個元素初始均為 0。有 m 條指令,要麼讓其中一段連續序列數字反轉——0 變 1,1 變 0(操作 1),要麼詢問某個元素的值(操作 2)。 1 ≤ n ≤ 10^5^, 1 ≤ m ≤ 5 × 10^5^
題解: 線段樹維護,每次給定反轉區間[a, b],那麼把[a, b]區間中每個數字加1,而後每次詢問x的時候,只需要query(1,1,n,x,x),而後判斷這個值是奇數還是偶數,奇數輸出1,偶數輸出0即可
程式碼:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const N = 1e5 + 10;
LL dat[N << 2], lazy[N << 2];
int n, m;
// 上傳標記,每次左右子樹建樹/區間修改完都需要上傳
void pushup(int rt) {
dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
}
// 建樹
void build(int rt, int l, int r) {
if (l == r) { // 遞迴到葉節點
dat[rt] = 0;
lazy[rt] = 0;
return;
}
// 遞迴建立左右子樹
int mid = (l + r) >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
pushup(rt); // 上傳
}
// 下傳,下傳標記,同時改變dat陣列
void pushdown(int rt, int l, int r) {
if (lazy[rt]) { // 如果有標記
int mid = (l + r) >> 1;
// 把標記給左右子樹
lazy[rt << 1] += lazy[rt];
lazy[rt << 1 | 1] += lazy[rt];
// 改變dat
dat[rt << 1] += (mid - l + 1) * lazy[rt];
dat[rt << 1 | 1] += (r - mid) * lazy[rt];
// rt標記清空
lazy[rt] = 0;
}
return;
}
// 區間修改: [L, R] += x
void modify(int rt, int l, int r, int L, int R, int x) {
if (L <= l && r <= R) { // 如果當前區間被完全包含
dat[rt] += (r - l + 1) * x; // 修改當前區間的dat值
lazy[rt] += x; // 改變懶標記
return ;
}
pushdown(rt, l, r); // 下傳
// 遞迴左右子樹修改區間
int mid = (l + r) >> 1;
if (L <= mid) modify(rt << 1, l, mid, L, R, x);
if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, x);
pushup(rt); // 上傳
return;
}
// 區間查詢:獲得[L, R]的區間和
LL query(int rt, int l, int r, int L, int R) {
if (L <= l && r <= R) return dat[rt]; // 如果[l, r]被完全包含於[L, R]
pushdown(rt, l, r); // 標記下傳
// 遞迴加上左右子樹
int mid = (l + r) >> 1;
LL res = 0;
if (L <= mid) res += query(rt << 1, l, mid, L, R);
if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);
return res;
}
int main() {
cin >> n >> m;
build(1, 1, n); // 建樹
for (int i = 1, a, b, x, op; i <= m; ++i) {
scanf("%d", &op);
if (op == 1) {
scanf("%d%d", &a, &b);
modify(1, 1, n, a, b, 1); // 區間修改, [a, b] += 1
}
else {
scanf("%d", &a);
printf("%lld\n", (query(1, 1, n, a, a) & 1) == 1); // 區間查詢,查詢[a, b]的區間和
}
}
return 0;
}
luogu P4588 [TJOI2018]數學計算
題意: 小豆現在有一個數x,初始值為1.小豆有Q次操作,操作有兩種型別:
1 m: x = x * m, 輸出x%mod;
2 pos:x= x = x / 第pos次操作所乘的數(保證第pos次操作一定為型別1,對於每一個型別1的操作至多會被除一次)輸出x % mod;Q <= 10^5^
題解: 使用線段樹維護1~Q這Q個數字的區間乘,如果當前是1型別操作,那麼進行單點修改modify(1, 1, n, i, x);如果是2型別操作,那麼進行單點修改modify(1, 1, n, pos, 1); 每次輸出都是所有的成績, 即dat[1];
程式碼:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const N = 1e5 + 10;
LL dat[N << 2];
int n, p, t;
void pushup(int rt) {
dat[rt] = dat[rt << 1] * dat[rt << 1 | 1] % p;
}
void build(int rt, int l, int r) {
if (l == r) {
dat[rt] = 1;
return;
}
int mid = (l + r) >> 1;
build(rt << 1, l, mid), build(rt << 1 | 1, mid + 1, r);
pushup(rt);
}
void modify(int rt, int l, int r, int x, int y) {
if (l == r && l == x) {
dat[rt] = y;
return;
}
int mid = (l + r) >> 1;
if (x <= mid) modify(rt << 1, l, mid, x, y);
else modify(rt << 1 | 1, mid + 1, r, x, y);
pushup(rt);
}
int main() {
cin >> t;
while (t--) {
cin >> n >> p;
for (int i = 1; i <= n * 4; ++i) dat[i] = 0;
build(1, 1, n);
for (int i = 1, op, x; i <= n; ++i) {
scanf("%d%d", &op, &x);
if (op == 1) modify(1, 1, n, i, x % p);
else modify(1, 1, n, x, 1);
printf("%lld\n", dat[1] % p);
}
}
return 0;
}
3.2 權值線段樹
luogu P1908 逆序對
題意: 求出一個數列的逆序對.數列長度n ≤ 5×10^5^
題解: 權值線段樹維護每個數字出現的次數,然後每個數字x出現的時候只需要區間查詢[1, x - 1]的出現次數即可
程式碼:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const N = 500050;
int n;
LL a[N], b[N];
int dat[N << 2];
LL ans = 0;
void modify(int rt, int l, int r, int x)
{
if(l == r)
{
dat[rt]++;
return;
}
int mid = (l + r) >> 1;
if(x <= mid) modify(rt << 1, l, mid, x);
else modify(rt << 1 | 1, mid + 1, r, x);
dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
}
int query(int rt,int l,int r,int L,int R)
{
if(L <= l && r <= R) return dat[rt];
int mid = (l + r) >> 1;
int res = 0;
if(L <= mid) res += query(rt << 1, l, mid, L, R);
if(mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);
return res;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
b[i] = a[i];
}
sort(b + 1, b + 1 + n);
int len = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= n; i++)
{
int pos = lower_bound(b + 1, b + n + 1, a[i]) - b;
a[i] = pos;
}
for(int i = 1; i <= n; i++)
{
int x = a[i];
ans += query(1, 1, n, x + 1, n);
modify(1, 1, n, x);
}
printf("%lld", ans);
return 0;
}