[學習筆記]吉司機線段樹


引入

  • 經典問題:給定一個序列,支持區間取 \(\min\)(給定 \(l,r,x\) 把所有滿足 \(l\le i\le r\)\(a_i\) 改成 \(\min(a_i,x)\))和區間求和

  • 要求在 \(O((n+q)\log n)\) 的時間內解決

算法流程

  • 吉司機線段樹是一種勢能線段樹,可以實現區間取 \(\min/\max\) 區間求和

  • \(\min\) 為例,線段樹上每個節點維護四個值:

  • (1)\(mx\):區間最大值

  • (2)\(cnt\):區間最大值的出現次數

  • (3)\(md\):區間次大值(嚴格小於最大值且最大的數)

  • (4)\(sum\):區間和

  • 實現區間取 \(\min\) 時,遞歸到線段樹上一個包含於詢問區間的節點 \(p\) 時,進行如下處理:

  • (1)若 \(x\ge mx_p\),則顯然這次修改不影響節點 \(p\),直接 return

  • (2)若 \(x\le md_p\),則暴力往 \(p\) 的左右子節點遞歸

  • (3)否則 \(md_p<x<mx_p\),這次修改對 \(sum_p\) 可以計算出為 \(cnt_p\times(mx_p-x)\),打標記即可

  • 考慮說明這個算法的復雜度。

  • 注意到區間取 \(\min\) 只會把區間內不同的數逐漸變成相同的

  • 易得對於線段樹上一個節點 \(p\),一次修改操作最多只會讓節點 \(p\) 代表的區間內不同數的種類數增加 \(1\)

  • 而修改操作時被暴力遞歸到的節點代表的區間內,不同數的種類數必然有減少

  • 也就是說,所有節點不同數種類數之和最多增加 \(O(q\log n)\) 次,必然也最多減少 \(O((n+q)\log n)\)

  • 故總時間復雜度 \(O((n+q)\log n)\)

與區間加的結合

  • 吉司機線段樹也可以和區間加相結合,只需多一個加法標記即可

  • 不過值得注意的是:如果有區間加操作則復雜度要多一個 \(\log\)

  • 分析略

CF1290E Cartesian Tree

題意

  • 給定一個 \(n\) 元排列 \(p\)

  • 對於每個 \(1\le i\le n\),求出這個排列中所有 \(\le i\) 的數構成的序列(維持相對位置關系)的笛卡爾樹(是大根堆)所有節點的子樹大小之和

  • \(1\le n\le 150000\)

做法

  • 對於一個 \(n\) 個元素的序列 \(a\),其笛卡爾樹上節點 \(i\) 的子樹大小為 \(nxt_i-pre_i-1\)\(pre_i\)\(nxt_i\) 分別為 \(a_i\) 左邊和右邊第一個大於 \(a_i\) 的位置,如果不存在則分別為 \(0\)\(n+1\)

  • 於是 \(i\) 從小到大維護所有 \(pre\)\(nxt\) 的和即可

  • 但為了方便維護,我們不動態維護序列,而是在一個長度為 \(n\) 的序列上把所有數從小到大激活,並且把 \(pre\)\(nxt\) 重新定義:

  • \(i\) 的左邊第一個比它大的被激活的位置為 \(j\),則 \(pre_i\) 等於 \(j\) 及其右邊被激活的總個數,如果 \(j\) 不存在則 \(pre_i\) 為所有被激活的個數加 \(1\)

  • \(i\) 的右邊第一個比它大的被激活的位置為 \(j\),則 \(nxt_i\) 等於 \(j\) 及其左邊被激活的總個數,如果 \(j\) 不存在則 \(nxt_i\) 為所有被激活的個數加 \(1\)

  • 這樣可以算出如果已經加入了前 \(i\) 小的數則答案為 \(\sum_j(pre_j+nxt_j)-i^2-2i\)

  • 當第 \(i\) 次在序列上第 \(x\) 位的數被激活(也就是 \(p_x=i\))時:

  • \(pre_x=nxt_x=i+1\),可以直接處理

  • \(y<x\),則 \(nxt_y\) 的值應當對 \(c\)\(\min\)\(c\) 為位置 \(x\) 左邊被激活的數的個數,包括 \(x\) 本身)

  • \(y>x\),則 \(nxt_y\) 應當加 \(1\)

  • 也就是要實現區間取 \(\min\) 和區間加,吉司機線段樹維護即可,\(pre\) 同理

  • \(O(n\log^2n)\)

Code

#include <bits/stdc++.h>
#define p2 p << 1
#define p3 p << 1 | 1

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

typedef long long ll;

const int N = 15e4 + 5, M = 6e5 + 5, INF = 0x3f3f3f3f;

int n, p[N], A[N];

void change(int x, int v)
{
	for (; x <= n; x += x & -x)
		A[x] += v;
}

int ask(int x)
{
	int res = 0;
	for (; x; x -= x & -x) res += A[x];
	return res;
}

struct elem
{
	int mx, cnt, md;
	
	friend inline elem operator + (elem a, elem b)
	{
		elem res;
		if (a.mx > b.mx) res = a, res.md = Max(a.md, b.mx);
		else if (a.mx < b.mx) res = b, res.md = Max(a.mx, b.md);
		else res = a, res.cnt = a.cnt + b.cnt, res.md = Max(a.md, b.md);
		return res;
	}
};

struct seg
{
	elem T[M]; int add[M], tag[M], cnt[M]; ll sum[M];
	
	void build(int l, int r, int p)
	{
		T[p] = (elem) {-INF, 0, -INF}; add[p] = 0; tag[p] = INF;
		if (l == r) return;
		int mid = l + r >> 1;
		build(l, mid, p2); build(mid + 1, r, p3);
	}
	
	void down(int p)
	{
		T[p2].mx += add[p]; T[p2].md += add[p]; sum[p2] += 1ll * cnt[p2] * add[p];
		add[p2] += add[p]; tag[p2] += add[p];
		T[p3].mx += add[p]; T[p3].md += add[p]; sum[p3] += 1ll * cnt[p3] * add[p];
		add[p3] += add[p]; tag[p3] += add[p];
		if (tag[p] < T[p2].mx) sum[p2] -= 1ll * T[p2].cnt * (T[p2].mx - tag[p]),
			tag[p2] = T[p2].mx = tag[p];
		if (tag[p] < T[p3].mx) sum[p3] -= 1ll * T[p3].cnt * (T[p3].mx - tag[p]),
			tag[p3] = T[p3].mx = tag[p];
		add[p] = 0; tag[p] = INF;
	}
	
	void upt(int p)
	{
		cnt[p] = cnt[p2] + cnt[p3]; sum[p] = sum[p2] + sum[p3];
		T[p] = T[p2] + T[p3];
	}
	
	void unlock(int l, int r, int pos, int v, int p)
	{
		if (l == r) return (void) (cnt[p] = T[p].cnt = 1, T[p].mx = sum[p] = v);
		int mid = l + r >> 1; down(p);
		if (pos <= mid) unlock(l, mid, pos, v, p2);
		else unlock(mid + 1, r, pos, v, p3);
		upt(p);
	}
	
	void change(int l, int r, int s, int e, int v, int p)
	{
		if (e < l || s > r) return;
		if (s <= l && r <= e) return (void) (add[p] += v, tag[p] += v,
			T[p].mx += v, T[p].md += v, sum[p] += 1ll * cnt[p] * v);
		int mid = l + r >> 1; down(p);
		change(l, mid, s, e, v, p2); change(mid + 1, r, s, e, v, p3);
		upt(p);
	}
	
	void modify(int l, int r, int s, int e, int v, int p)
	{
		if (e < l || s > r) return;
		if (s <= l && r <= e && v > T[p].md)
		{
			if (v < T[p].mx) sum[p] -= 1ll * T[p].cnt * (T[p].mx - v),
				tag[p] = T[p].mx = v;
			return;
		}
		int mid = l + r >> 1; down(p);
		modify(l, mid, s, e, v, p2); modify(mid + 1, r, s, e, v, p3);
		upt(p);
	}
} T1, T2;

int main()
{
	int x;
	read(n);
	for (int i = 1; i <= n; i++) read(x), p[x] = i;
	T1.build(1, n, 1); T2.build(1, n, 1);
	for (int i = 1; i <= n; i++)
	{
		change(p[i], 1); int c = ask(p[i]);
		T1.modify(1, n, 1, p[i], c, 1); T1.change(1, n, p[i], n, 1, 1);
		T2.modify(1, n, p[i], n, i - c + 1, 1); T2.change(1, n, 1, p[i], 1, 1);
		T1.unlock(1, n, p[i], i + 1, 1); T2.unlock(1, n, p[i], i + 1, 1);
		printf("%lld\n", T1.sum[1] + T2.sum[1] - 1ll * i * (i + 2));
	}
	return 0;
}

區間 K 小值

題意

  • 一個長度為 \(n\) 的序列,所有數都是 \([1,n]\) 內的正整數

  • \(n\) 次操作,每次操作為區間取 \(\min\) 或求區間第 \(k\) 小值

  • \(1\le n,m\le 8\times10^4\)

做法

  • 容易想到位置線段樹套權值線段樹

  • 查詢時把詢問區間拆成的 \(O(\log n)\) 個外層節點拎出來,在這些節點內嵌的線段樹上二分即可

  • 對於修改,外層的線段樹是可以打上取 \(\min\) 的標記的,但這里存在的問題是:對於打上標記的節點,其祖先節點內嵌的線段樹無法快速實現修改。

  • 於是考慮和吉司機線段樹一樣的思想:把相同的數放到一起修改。

  • 修改時對於在外層線段樹上拆出的所有 \(O(\log n)\) 個節點,找出這個節點上所有本質不同的大於 \(x\) 的數,在這個節點及其祖先上把這些數改為 \(x\) 即可

  • 注意到每把這個節點內本質不同的數的個數減 \(1\),都要在 \(O(\log n)\) 棵內層線段樹上做修改,也就是需要 \(O(\log^2n)\) 的復雜度

  • 故總復雜度 \(O((n+q)\log^3n)\)

Code

#include <bits/stdc++.h>
#define p2 p << 1
#define p3 p << 1 | 1

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

const int N = 8e4 + 5, M = N << 2, L = 3e7 + 5;

int n, m, a[N], rt[M], tag[M], ToT, tot, pt[N], nc, del[L], qaq, pos[N], val[N];

struct seg
{
	int lc, rc, sum;
} T[L];

inline int newnode() {return nc ? del[nc--] : ++ToT;}

inline void delnode(int p) {T[p].lc = T[p].rc = T[p].sum = 0; del[++nc] = p;}

void change(int l, int r, int pos, int v, int &p)
{
	if (!v) return;
	if (!p) p = newnode(); T[p].sum += v;
	if (l == r) return;
	int mid = l + r >> 1;
	if (pos <= mid) change(l, mid, pos, v, T[p].lc);
	else change(mid + 1, r, pos, v, T[p].rc);
}

void dfs(int p)
{
	if (!p) return;
	dfs(T[p].lc); dfs(T[p].rc);
	delnode(p);
}

int ask(int l, int r, int x, int p)
{
	if (l == r) return 0;
	int mid = l + r >> 1, res;
	if (x <= mid) res = ask(l, mid, x, T[p].lc) + T[T[p].rc].sum,
		dfs(T[p].rc), T[p].rc = 0;
	else res = ask(mid + 1, r, x, T[p].rc);
	return T[p].sum = T[T[p].lc].sum + T[T[p].rc].sum, res;
}

void zzq(int x, int &p) {change(1, n, x, ask(1, n, x, p), p);}

void build(int l, int r, int p)
{
	tag[p] = n;
	for (int i = l; i <= r; i++) change(1, n, a[i], 1, rt[p]);
	if (l == r) return;
	int mid = l + r >> 1;
	build(l, mid, p2); build(mid + 1, r, p3);
}

void down(int p)
{
	tag[p2] = Min(tag[p2], tag[p]);
	tag[p3] = Min(tag[p3], tag[p]);
	zzq(tag[p], rt[p2]); zzq(tag[p], rt[p3]);
	tag[p] = n;
}

void zhouzhouzka(int l, int r, int x, int p)
{
	if (x > r || !T[p].sum) return;
	if (l == r) return (void) (pos[++qaq] = l, val[qaq] = T[p].sum);
	int mid = l + r >> 1;
	zhouzhouzka(l, mid, x, T[p].lc);
	zhouzhouzka(mid + 1, r, x, T[p].rc);
}

void getmin(int l, int r, int s, int e, int x, int p)
{
	if (e < l || s > r) return;
	if (s <= l && r <= e)
	{
		tag[p] = Min(tag[p], x); qaq = 0; zhouzhouzka(1, n, x + 1, rt[p]);
		zzq(x, rt[p]);
		for (int i = 1; i <= qaq; i++)
			for (int q = p >> 1; q; q >>= 1)
				change(1, n, pos[i], -val[i], rt[q]),
					change(1, n, x, val[i], rt[q]);
		return;
	}
	int mid = l + r >> 1; down(p);
	getmin(l, mid, s, e, x, p2); getmin(mid + 1, r, s, e, x, p3);
}

void czx(int l, int r, int s, int e, int x, int p)
{
	if (e < l || s > r) return;
	if (s <= l && r <= e) return (void) (pt[++tot] = rt[p]);
	int mid = l + r >> 1; down(p);
	czx(l, mid, s, e, x, p2); czx(mid + 1, r, s, e, x, p3);
}

int query(int l, int r, int k)
{
	tot = 0; czx(1, n, l, r, n, 1);
	int d = 0;
	for (int i = 1; i <= tot; i++) d += T[pt[i]].sum;
	l = 1; r = n;
	while (l < r)
	{
		int delta = 0, mid = l + r >> 1;
		for (int i = 1; i <= tot; i++) delta += T[T[pt[i]].lc].sum;
		if (k <= delta)
		{
			r = mid;
			for (int i = 1; i <= tot; i++) pt[i] = T[pt[i]].lc;
		}
		else
		{
			k -= delta; l = mid + 1;
			for (int i = 1; i <= tot; i++) pt[i] = T[pt[i]].rc;
		}
	}
	return l;
}

int main()
{
	int op, l, r, x;
	read(n); read(m);
	for (int i = 1; i <= n; i++) read(a[i]);
	build(1, n, 1);
	while (m--)
	{
		read(op); read(l); read(r); read(x);
		if (op == 1 && x > n) x = n;
		if (op == 1) getmin(1, n, l, r, x, 1);
		else printf("%d\n", query(l, r, x));
	}
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM