線段樹維護單調棧——區間查詢版本 & 維護遞減序列

LCat90發表於2024-08-23

這次算是完全搞懂了吧()()(

其實追溯到了單調棧的一些本質問題,而且搬到笛卡爾樹上特別清楚了。

你線段樹維護單調棧上升/下降,本質上是維護笛卡爾樹裡面一個點/根的最長左鏈/右鏈。

#include <bits/stdc++.h>
#define raed read
#define cacl calc
#define pb push_back
#define pii pair <int, int>
#define int long long
#define fi first 
#define se second
#define ls p << 1
#define rs p << 1 | 1
using namespace std;
const int N = 2e5 + 5, M = 20; int read();

int n, la[N], lb[N];
struct node {
	int x, y, id;
} p[N]; 

int ans;

struct sT {
	int maxn, sum;
} T[N << 2];

int get(int p, int l, int r, int x) { // 選的數 > x 
	if(l > r) return 0;
	if(T[p].maxn <= x) return 0;
	if(l == r) return T[p].maxn > x; int mid = l + r >> 1;
	if(T[rs].maxn <= x) return get(ls, l, mid, x);
	return get(rs, mid + 1, r, x) + T[p].sum - T[rs].sum;
}

void update(int p, int k, int x, const int l = 1, const int r = n) {
	if(l == r) return T[p].sum = 1, T[p].maxn = x, void();
	int mid = l + r >> 1;
	if(k <= mid) update(ls, k, x, l, mid);
	else update(rs, k, x, mid + 1, r);
	T[p].maxn = max(T[ls].maxn, T[rs].maxn); 
	int sb = get(ls, l, mid, T[rs].maxn);
	T[p].sum = T[rs].sum + sb; 
}

int now;
int query(int p, int ql, int qr, const int l = 1, const int r = n) { 
	if(ql <= l and r <= qr) {
		int w = get(p, l, r, now);
		now = max(now, T[p].maxn);
		return w;
	}
	int mid = l + r >> 1, ans = 0;
	if(qr > mid) ans += query(rs, ql, qr, mid + 1, r);
	if(ql <= mid) ans += query(ls, ql, qr, l, mid);
	return ans;
}

void solve() {
	cin >> n;
	for(int i = 1;i <= n; ++i) p[i].x = read(), p[i].y = read();
	sort(p + 1, p + n + 1, [&](node a, node b){ return a.x < b.x; });
	for(int i = 1;i <= n; ++i) la[i] = p[i].x, lb[i] = p[i].y;
	sort(la + 1, la + n + 1); sort(lb +1 , lb + n + 1);
	for(int i = 1;i <= n; ++i) 
		p[i].y = lower_bound(lb + 1, lb + n + 1, p[i].y) - lb, p[i].id = i;
	
	sort(p + 1, p + n + 1, [&](node a, node b){ return a.y < b.y; });	
	
	
	for(int i = 1;i <= n; ++i) {
		now = 0;
		ans += query(1, 1, p[i].id);
		update(1, p[i].id, i);	
	}
		
	cout << ans << "\n";
} 


signed main() {
	int T = 1;
	while(T--) solve();
    return 0;
}

/*
對當前點字首 <= y 的點做單調棧 
類似線段樹維護單調棧考慮轉換到 CDQ 上,每個點維護當前答案和字首最小值
你可以找到排序後的那個位置 
按照 y 的大xiao動態加,每次計算答案即可 
線段樹維護單調棧怎麼做 
*/

int read() {
   char c; int f = 1, sum = 0;
   while(c < '0' or c > '9') {if(c == '-') f = -1;c = getchar();}
   while(c >= '0' and c <= '9') {sum = (sum << 3) + (sum << 1) + (c ^ 48);c = getchar();} 
   return sum * f;
}

相關文章