1. 程式人生 > >【bzoj4540】[Hnoi2016]序列 單調棧+離線+掃描線+樹狀數組區間修改

【bzoj4540】[Hnoi2016]序列 單調棧+離線+掃描線+樹狀數組區間修改

sta 輸出 char algo .com legend 方法 tle leg

題目描述

給出一個序列,多次詢問一個區間的所有子區間最小值之和。

輸入

輸入文件的第一行包含兩個整數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]序列 單調棧+離線+掃描線+樹狀數組區間修改