https://www.luogu.com.cn/problem/P10516
第3題 資料結構 檢視測評資料資訊
給定兩個長度為 n 的序列 a[i] 和 b[i]。有以下三種操作:
1. 給定區間 [l,r] 以及引數 k,t,把區間內滿足 a[i]* b[i]<= k 的位置的 a[i] 和 b[i] 分別加上 t。
2. 給定 i 和 x,y,將 a[i] 改為 x,b[i] 改為 y。
3. 查詢區間內每個位置 a[i]+b[i] 的和。
輸入格式
第一行包含兩個整數 n,m,分別表示該數列數字的個數和操作的總個數。
第二行包含 n 個用空格分隔的整數,其中第 i 個數字表示 a[i]。
第三行包含 n 個用空格分隔的整數,其中第 i 個數字表示 b[i]。
接下來 m 行每行包含 3 到 5 個整數,表示一個操作,具體如下:
1. 1 l r k t:將區間 [l,r] 進行一操作。
2. 2 i x y:將 a[i] 改為 x,b[i] 改為 y。
3. 3 l r:輸出區間 [l,r] 內每個數的和。
1<= n,m<= 1e5,0<= a[i],b[i],k,t,x,y<=1e5
輸出格式
若干行,每行表示操作 3 的答案。
輸入/輸出例子1
輸入:
5 5
23 4 3 3 7
54 29 7 1 2
1 1 5 114 1
2 2 7 9
3 1 5
3 1 2
3 3 4
輸出:
122
93
18
樣例解釋
第一次修改後,序列 a[i] 為:{23,4,4,4,8};序列 b[i] 為 {54,29,8,2,3}。
第二次修改後,序列 a[i] 為:{23,7,4,4,8};序列 b[i] 為 {54,9,8,2,3}。
勢能線段樹概念(重要)
本質上是一種區間暴力修改技術。
考慮普通的區修線段樹,我們使用 lazytag 來記錄區間修改資訊,在訪問到當前節點之後再將當前節點的 tag 下傳。
然而,有很多資訊是不好下傳的(不滿足交換或結合律),於是我們就無法使用 tag 來記錄區修資訊。但有的題目每一個操作都有潛在的操作次數上限,我們可以先判斷該區間有沒有需要操作的節點,有的話遍歷到葉子節點進行修改,沒有的話返回。
單調操作,區間查詢,很容易想到線段樹
操作二,三都是板子,我們關注一下操作一
分析一下操作一
看上去是區間修改,但是下放lazy_tag發現很難,對於修改也很難(不知具體改哪一個點),原因就是此操作不滿足區間性(區間的每個值同時加減乘除等等吧)。所以考慮單點修改
由於每次操作完
a[i]*b[i] 都變成 (a[i]+t)*(b[i]+t)
我們化簡。a[i]*b[i] + a[i]*t + b[i]*t + t*t
注意到最後一項,這樣就說明這個值的增長是非常快的,操作次數不超過 sqrt(k) 就可以到上限
但是注意,這裡 t不為0,才能增長,否則就是無意義的。
還有一點,要維護一個區間最小值,因為只有<=k的值才能進行修改。
#include <bits/stdc++.h> #define int long long using namespace std; const int N=5e5+5; struct node { int Min, sum; }tr[N]; int n, m, x, y, op, a[N], b[N], L, R, k, t; void push_up(int id) { tr[id].sum=tr[id*2].sum+tr[id*2+1].sum; tr[id].Min=min(tr[id*2].Min, tr[id*2+1].Min); } void build(int id, int L, int R) { if (L==R) { tr[id].sum=a[L]+b[L]; tr[id].Min=a[L]*b[L]; return ; } int mid=(L+R)>>1; build(id*2, L, mid); build(id*2+1, mid+1, R); push_up(id); } void change(int id, int L, int R, int x, int y, int k, int t) { if (tr[id].Min>k) return ; if (L==R) { a[L]+=t; b[L]+=t; tr[id].sum=a[L]+b[L]; tr[id].Min=a[L]*b[L]; return ; } int mid=(L+R)>>1, flag=1; if (x<=mid) change(id*2, L, mid, x, y, k, t); if (y>=mid+1) change(id*2+1, mid+1, R, x, y, k, t); push_up(id); } void change2(int id, int L, int R, int x, int v, int v2) { if (L==R) { a[x]=v; b[x]=v2; tr[id].sum=a[L]+b[L]; tr[id].Min=a[L]*b[L]; return ; } int mid=(L+R)>>1; if (x<=mid) change2(id*2, L, mid, x, v, v2); else change2(id*2+1, mid+1, R, x, v, v2); push_up(id); } int ask(int id, int L, int R, int x, int y) { if (x<=L && R<=y) return tr[id].sum; int mid=(L+R)>>1, res=0; if (x<=mid) res+=ask(id*2, L, mid, x, y); if (y>=mid+1) res+=ask(id*2+1, mid+1, R, x, y); return res; } signed main() { scanf("%lld%lld", &n, &m); for (int i=1; i<=n; i++) scanf("%lld", &a[i]); for (int i=1; i<=n; i++) scanf("%lld", &b[i]); build(1, 1, n); while (m--) { scanf("%lld", &op); if (op==1) { scanf("%lld%lld%lld%lld", &L, &R, &k, &t); if (t==0) continue; change(1, 1, n, L, R, k, t); } else if (op==2) { scanf("%lld%lld%lld", &k, &L, &R); change2(1, 1, n, k, L, R); } else { scanf("%lld%lld", &L, &R); printf("%lld\n", ask(1, 1, n, L, R)); } } return 0; }