區間歷史最值筆記——線段樹


例題

CPU監控
要你維護對序列上的操作:
1、區間加
2、區間賦值
3、區間最大值
4、區間歷史最值

使用線段樹+標記維護,記錄節點上發生的所有事件。

注意到一個線段樹節點,如果進行了modify操作,那么接下來的加法都可以認為是modify。

那么一個節點上的標記長度就至多為2了。

\(\text{add}\) 標記時節點實際要加的值,\(\text{mod}\) 表示覆蓋。
考慮記錄 \(\text{Add}\) 標記為所有祖先的 \(\text{add}\) 標記,歷史上能達到的最大值。\(\text{Mod}\) 同理。
下放標記時注意到子節點上的標記發生時間在該點之前,依此時間順序進行合並如下:

當下放加法標記,一方面由於 \(\text{Add}\) 是上方加法操作中最大增加的值,用它結合實際值來更新歷史最值
若子節點無 \(\text{mod}\) 標記那么標記向 \(\text{add}\) 上打,否則向 \(\text{mod}\) 上打。
並且要結合該子節點當前的操作值,更新對應操作的歷史最值

當下放覆蓋標記,先用 \(\text{Mod}\) (歷史最大覆蓋值)更新子節點的歷史最值和 \(\text{Mod}\)
然后用實際覆蓋值修改實際最大值和實際覆蓋值。

下放標記要注意順序,加法在先,賦值在后。由於該寫法下放標記的操作數量很小,可能存在速度優勢。

下面是這個題的代碼,它在2020年1月19日是洛谷的Rank1.

#include<stdio.h>
#include<algorithm>
using namespace std;

const int N = 100005;
const int inf = 1e9;

struct IO_tp
{
	static const int Sbuf=1<<21;
	char buf[Sbuf], *S, *T, c; int f;
	#define gc() (S==T?(T=(S=buf)+fread(buf,1,sizeof(buf),stdin),(S==T?EOF:*S++)):*S++)
	template<class I>
	inline IO_tp& operator >> (I &x)
	{
		for(f=1, c=gc(); c<'0'||c>'9'; c=gc()) if(c=='-') f=-1;
		for(x=0; c>='0'&&c<='9'; c=gc()) x=x*10+(c^48); x*=f;
		return *this;
	}
	inline char Readc()
	{
		for(c=gc(); c<'A'||c>'Z'; c=gc());
		return c;
	}
}io;

inline void ckmax(int&x,const int y)
{ x = x < y ? y : x; }

struct Segtree
{
	struct node {
		int max, add, mod;
		int Max, Add, Mod;
		node(): mod(-inf), Mod(-inf) {}
	}t[N << 2];
	
	#define lc (o << 1)
	#define rc (o << 1 | 1)
	
	void pushup(int o)
	{
		t[o].max = max(t[lc].max, t[rc].max);
		t[o].Max = max(t[lc].Max, t[rc].Max);
	}
	
	void dadd(int o, int x, int y)
	{
		ckmax(t[o].Max, t[o].max + x);
		t[o].max += y;
		if(t[o].mod == -inf)
			ckmax(t[o].Add, t[o].add + x), t[o].add += y;
		else
			ckmax(t[o].Mod, t[o].mod + x), t[o].mod += y;
	}
	
	void dmod(int o, int x, int y)
	{
		ckmax(t[o].Max, x);
		ckmax(t[o].Mod, x);
		t[o].mod = t[o].max = y;
	}
	
	void pushdown(int o)
	{
		if(t[o].add || t[o].Add)
		{
			dadd(lc, t[o].Add, t[o].add);
			dadd(rc, t[o].Add, t[o].add);
			t[o].Add = t[o].add = 0;
		}
		if(t[o].mod != -inf)
		{
			dmod(lc, t[o].Mod, t[o].mod);
			dmod(rc, t[o].Mod, t[o].mod);
			t[o].Mod = t[o].mod = -inf;
		}
	}
	
	void build(int o, int l, int r)
	{
		if(l == r)
		{
			int k; io >> k;
			t[o].max = t[o].Max = k;
			return;
		}
		int mid = (l + r) >> 1;
		build(lc, l, mid); build(rc, mid + 1, r);
		pushup(o);
	}
	
	void update(int o, int l, int r, int pl, int pr, int x)
	{
		if(l > pr || r < pl)
			return;
		if(l >= pl && r <= pr)
		{
			dadd(o, x, x);
			return;
		}
		pushdown(o);
		int mid = (l + r) >> 1;
		update(lc, l, mid, pl, pr, x);
		update(rc, mid + 1, r, pl, pr, x);
		pushup(o);
	}
	
	void modify(int o, int l, int r, int pl, int pr, int x)
	{
		if(l > pr || r < pl)
			return;
		if(l >= pl && r <= pr)
		{
			dmod(o, x, x);
			return;
		}
		pushdown(o);
		int mid = (l + r) >> 1;
		modify(lc, l, mid, pl, pr, x);
		modify(rc, mid + 1, r, pl, pr, x);
		pushup(o);
	}
	
	int query(int o, int l, int r, int pl, int pr)
	{
		if(l > pr || r < pl)
			return -inf;
		if(l >= pl && r <= pr)
			return t[o].max;
		pushdown(o);
		int mid = (l + r) >> 1;
		return max(query(lc, l, mid, pl, pr), query(rc, mid + 1, r, pl, pr));
	}
	
	int Query(int o, int l, int r, int pl, int pr)
	{
		if(l > pr || r < pl)
			return -inf;
		if(l >= pl && r <= pr)
			return t[o].Max;
		pushdown(o);
		int mid = (l + r) >> 1;
		return max(Query(lc, l, mid, pl, pr), Query(rc, mid + 1, r, pl, pr));
	}
}z[1];

int main()
{
	freopen("cpu.in", "r", stdin);
	freopen("cpu.out", "w", stdout);
	int n, q, l, r, x; char opt[3];
	io >> n;
	z->build(1, 1, n);
	io >> q;
	while(q--)
	{
		opt[0] = io.Readc();
		io >> l >> r;
		if(*opt == 'Q')
			printf("%d\n", z->query(1, 1, n, l, r));
		if(*opt == 'A')
			printf("%d\n", z->Query(1, 1, n, l, r));
		if(*opt == 'P')
			io >> x, z->update(1, 1, n, l, r, x);
		if(*opt == 'C')
			io >> x, z->modify(1, 1, n, l, r, x);
	}
	return 0;
}

上面的做法有局限性。
設分段函數 \(f(x)=max(x+a,b)\),則區間修改操作均可以寫成對一個區間作用上一個函數。
並且 \(h(x)=max(f(x),g(x))\) 也依然是這樣的函數。
那么我們可以維護區間的分段函數 \(f(x)\) 和當前所有函數“最高輪廓” \(g(x)\)
考慮下放標記,\(f(x)\) 直接合並即可,考慮 \(g(x)\)
相當於一堆函數作用到一個函數 \(f'\) 上,最高的輪廓應當是這堆函數的 \(g\) 作用於 \(f\),然后再把這個和之前的 \(g'\) 取較高的。
若要維護區間歷史最值,下放標記時用 \(g(\text{max})\) 更新 \(\text{Max}\);再令 \(\text{max}=f(\text{max})\)
要注意你這個操作是對於這個區間整體而言的,就是說該區間的歷史最值肯定要找最靠右的位置,所以要用當時的 \(\text{max}\) 更新。

下面是清華集訓那題的代碼,只有單點查詢(區間歷史最值只要在 pushdown 里加兩句話)

#include<cstdio>
#include<algorithm>
using namespace std;

typedef long long ll;
const int N = 500004;
const ll inf = 1e16;

inline void ckmax(ll&x, ll y)
{ x = x < y ? y : x; }

struct IO_tp
{
	static const int Sbuf=1<<21;
	char buf[Sbuf], *S, *T, c; int f;
	#define gc() (S==T?(T=(S=buf)+fread(buf,1,sizeof(buf),stdin),(S==T?EOF:*S++)):*S++)
	template<class I>
	inline IO_tp& operator >> (I &x)
	{
		for(f=1, c=gc(); c<'0'||c>'9'; c=gc()) if(c=='-') f=-1;
		for(x=0; c>='0'&&c<='9'; c=gc()) x=x*10+(c^48); x*=f;
		return *this;
	}
	inline char Readc()
	{
		for(c=gc(); c<'A'||c>'Z'; c=gc());
		return c;
	}
}io;

int n, m, a[N];

struct node {
	ll a, b;
	node(ll p = 0, ll q = -inf): a(p), b(q) {}
	
	node operator + (const node& t)
	{ return node(max(a + t.a, -inf), max(b + t.a, t.b)); }
	
	ll operator () (ll x)
	{ return max(x + a, b); }
	
	void operator |= (const node& t)
	{
		ckmax(a, t.a);
		ckmax(b, t.b);
	}
} null;

struct Segtree
{
	#define lc (o << 1)
	#define rc (o << 1 | 1)
	
	node f[N << 2], g[N << 2];
	
	void Z(int o, const node& p, const node& q)
	{
		f[o] |= g[o] + p;
		g[o] = g[o] + q;
	}
	
	void pushdown(int o)
	{
		Z(lc, f[o], g[o]); Z(rc, f[o], g[o]);
		f[o] = g[o] = null;
	}
	
	void modify(int o, int l, int r, int pl, int pr, const node& z)
	{
		if(l > pr || r < pl)
			return;
		if(l >= pl && r <= pr)
		{
			Z(o, z, z);
			return;
		}
		pushdown(o);
		int mid = (l + r) >> 1;
		modify(lc, l, mid, pl, pr, z);
		modify(rc, mid + 1, r, pl, pr, z);
	}
	
	ll query(int o, int l, int r, int p)
	{
		if(l == r)
			return g[o](a[l]);
		pushdown(o);
		int mid = (l + r) >> 1;
		return mid >= p ? query(lc, l, mid, p) : query(rc, mid + 1, r, p);
	}
	
	ll Query(int o, int l, int r, int p)
	{
		if(l == r)
			return f[o](a[l]);
		pushdown(o);
		int mid = (l + r) >> 1;
		return mid >= p ? Query(lc, l, mid, p) : Query(rc, mid + 1, r, p);
	}
}z[1];

int main()
{
	io >> n >> m;
	for(int i = 1; i <= n; ++i)
		io >> a[i];
	while(m--)
	{
		int opt, l, r, x;
		io >> opt;
		if(opt <= 3)
		{
			io >> l >> r >> x;
			if(opt == 1)
				z->modify(1, 1, n, l, r, node(x, 0));
			else if(opt == 2)
				z->modify(1, 1, n, l, r, node(-x, 0));
			else
				z->modify(1, 1, n, l, r, node(-inf, x));
		}
		else
		{
			io >> x;
			if(opt == 4)
				printf("%lld\n", z->query(1, 1, n, x));
			else
				printf("%lld\n", z->Query(1, 1, n, x));
		}
	}
	return 0;
}


免責聲明!

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



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