【bzoj4540】[Hnoi2016]序列 單調棧+離線+掃描線+樹狀數組區間修改
題目描述
給出一個序列,多次詢問一個區間的所有子區間最小值之和。
輸入
輸入文件的第一行包含兩個整數n和q,分別代表序列長度和詢問數。接下來一行,包含n個整數,以空格隔開,第i個整數為ai,即序列第i個元素的值。接下來q行,每行包含兩個整數l和r,代表一次詢問。
輸出
對於每次詢問,輸出一行,代表詢問的答案。
樣例輸入
5 5
5 2 4 1 3
1 5
1 3
2 4
3 5
2 5
樣例輸出
28
17
11
11
17
題解
單調棧+離線+掃描線+樹狀數組區間修改
首先把使用單調棧找出每個數左邊第一個大於等於它的數的位置 $lp[i]$ 和右邊第一個大於它的數的位置 $rp[i]$ 。
然後每個數的貢獻為:左端點在 $[lp[i]+1,i]$ ,右端點在 $[i,rp[i]-1]$ 的所有區間。
如果把區間看作二維平面上的點的話,每個數的貢獻相當於是一個矩形,查詢範圍也是一個矩形。因此問題轉化為矩形加、查詢矩形和。
可以使用樹狀數組區間修改來維護,具體方法可以參考 【bzoj3132】上帝造題的七分鐘 。
然而本題坐標範圍較大,不能直接上二維樹狀數組,需要使用掃描線去掉一維,然後使用樹狀數組解決。方法和那道題一樣,維護 $\sum v_i,\sum x_iv_i,\sum y_iv_i,\sum x_iy_iv_i$ 即可。
因此離線處理,把每個修改矩形、詢問矩形都拆成4個點,放到一起排序,然後掃一遍統計答案即可。(一個小技巧:由於區間左端點一定小於等於右端點,因此整個平面只有橫坐標小於等於縱坐標的才有意義,因此可以只把詢問矩形拆成兩個點)
時間復雜度 $O(n\log n)$
#include <cstdio> #include <cctype> #include <cstring> #include <algorithm> #define N 100010 using namespace std; typedef long long ll; int n , a[N] , sta[N] , top , lp[N] , rp[N] , tot; ll ans[N]; struct bit { ll v[N]; inline void add(int x , ll a) { int i; for(i = x ; i <= n ; i += i & -i) v[i] += a; } inline ll query(int x) { int i; ll ans = 0; for(i = x ; i ; i -= i & -i) ans += v[i]; return ans; } }A , B , C , D; struct data { int x , y , opt , c; data() {} data(int p , int q , int r , int s) {x = p , y = q , opt = r , c = s;} bool operator<(const data &a)const {return y == a.y ? !opt && a.opt : y < a.y;} }p[N * 6]; inline void modify(int x , int y , ll a) { A.add(x , a) , B.add(x , a * x) , C.add(x , a * y) , D.add(x , a * x * y); } inline ll solve(int x , int y) { return A.query(x) * (x + 1) * (y + 1) - B.query(x) * (y + 1) - C.query(x) * (x + 1) + D.query(x); } inline int read() { int ret = 0 , f = 0; char ch = getchar(); while(!isdigit(ch)) f |= (ch == ‘-‘) , ch = getchar(); while(isdigit(ch)) ret = ((ret + (ret << 2)) << 1) + (ch ^ ‘0‘) , ch = getchar(); return f ? -ret : ret; } int main() { n = read(); int m = read() , i , l , r; for(i = 1 ; i <= n ; i ++ ) a[i] = read(); top = 0 , sta[0] = 0; for(i = 1 ; i <= n ; i ++ ) { while(top && a[i] < a[sta[top]]) top -- ; lp[i] = sta[top] + 1 , sta[++top] = i; } top = 0 , sta[0] = n + 1; for(i = n ; i ; i -- ) { while(top && a[i] <= a[sta[top]]) top -- ; rp[i] = sta[top] - 1 , sta[++top] = i; } for(i = 1 ; i <= n ; i ++ ) { p[++tot] = data(lp[i] , i , 0 , a[i]); p[++tot] = data(i + 1 , i , 0 , -a[i]); p[++tot] = data(lp[i] , rp[i] + 1 , 0 , -a[i]); p[++tot] = data(i + 1 , rp[i] + 1 , 0 , a[i]); } for(i = 1 ; i <= m ; i ++ ) { l = read() - 1 , r = read(); p[++tot] = data(r , r , 1 , i); p[++tot] = data(l , r , -1 , i); } sort(p + 1 , p + tot + 1); for(i = 1 ; i <= tot ; i ++ ) { if(p[i].opt) ans[p[i].c] += p[i].opt * solve(p[i].x , p[i].y); else modify(p[i].x , p[i].y , p[i].c); } for(i = 1 ; i <= m ; i ++ ) printf("%lld\n" , ans[i]); return 0; }
【bzoj4540】[Hnoi2016]序列 單調棧+離線+掃描線+樹狀數組區間修改