Loading

「刷题记录」P7476 「C.E.L.U-02」苦涩

题目背景

回想起自己的过往的人生,\(\text{YQH}\) 觉得心中充满了苦涩。如果人生能再来一次,我一定会少做一些傻事,少真香几次,然后大胆地去追寻自己的爱。可惜没有这样一个机会了。

题目描述

\(\text{YQH}\) 的梦中,他看到自己过去的记忆正在不断浮现在自己脑中。这些记忆带给他的是满满的苦涩。他想要强行忘记一些来减轻自己的苦涩。
\(\text{YQH}\) 的脑中可以被分成 \(n\) 个片区,每个片区相当于一个存放记忆的可重集,初始为空。他将进行 \(m\) 次这三种操作:
操作 \(1\):区间 \(l\sim r\) 的片区中都浮现了一个苦涩值为 \(k\) 的记忆。
操作 \(2\)\(\text{YQH}\) 开始清理 \(l\sim r\) 片区的记忆。如果一个片区 \(k\in[l,r]\)\(k\) 中苦涩值最大的记忆与 \(l\sim r\) 片区中苦涩值最大的记忆相等,则将这个苦涩值最大的记忆忘记。如果在同一个片区有多个相同的苦涩值最大的记忆,则只忘记一个。如果这些片区内没有记忆,则无视。
操作 \(3\)\(\text{YQH}\) 想知道,\(l\sim r\) 片区中苦涩值最大的记忆的苦涩值是多少,如果不存在,输出 \(-1\)

输入格式

第一行两个数,\(n,m\)
接下来 \(m\) 行,第一个数代表操作种类 \(op\),对于操作 \(1\),有三个数 \(l,r,k\),对于操作 \(2\)\(3\),有两个数 \(l,r\)

输出格式

对于每个操作 \(3\) 输出一行,代表答案。

样例 #1

样例输入 #1

5 4
1 1 3 2
1 2 4 3
2 3 3
3 1 3

样例输出 #1

3

样例 #2

样例输入 #2

6 6
1 1 6 2
1 3 3 2
1 3 4 3
2 3 4
3 3 3
3 4 4

样例输出 #2

2
2

提示

样例解释

样例解释一

下面为各操作之后 \(\text{YQH}\) 的大脑的状态:
第一次操作:\(\{2\},\{2\},\{2\},\varnothing,\varnothing\)
第二次操作:\(\{2\},\{2,3\},\{2,3\},\{3\},\varnothing\)
第三次操作:\(\{2\},\{2,3\},\{2\},\{3\},\varnothing\)
第四次操作询问 区间 \(1\sim 3\) 的最大值,所以答案是 \(3\)

样例解释二

下面为各操作之后 \(\text{YQH}\) 的大脑的状态:
第一次操作:\(\{2\},\{2\},\{2\},\{2\},\{2\},\{2\}\)
第二次操作:\(\{2\},\{2\},\{2,2\},\{2\},\{2\},\{2\}\)
第三次操作:\(\{2\},\{2\},\{2,2,3\},\{2,3\},\{2\},\{2\}\)
第四次操作:\(\{2\},\{2\},\{2,2\},\{2\},\{2\},\{2\}\)
第五次操作询问 \(3\) 的最大值,所以答案是 \(2\)
第六次操作询问 \(4\) 的最大值,所以答案是 \(2\)

数据范围

Subtask n m 特殊性质
\(1(10pts)\) \(\leq10^3\) \(\le10^3\) \(\diagdown\)
\(2(20pts)\) \(\leq5\times10^4\) \(\leq5\times10^4\) 没有操作 2
\(3(10pts)\) \(\leq5\times10^4\) \(\leq5\times10^4\) 操作 2 中 \(l=r\)
\(4(20pts)\) \(\leq5\times10^4\) \(\leq5\times10^4\) \(\diagdown\)
\(5(20pts)\) \(\leq2\times10^5\) \(\leq2\times10^5\) 操作 2 中 \(l=r\)
\(6(20pts)\) \(\leq2\times10^5\) \(\leq2\times10^5\) \(\diagdown\)

对于 \(100\%\) 的数据,\(n,m\le2\times10^5,k\le10^9\)

思路

一共有三个操作,别看他描述的花里胡哨的,其实就是区间加,区间删除,区间查询。
但是,由于区间加的操作,它的叶节点存储的不止有一个数字,而是一个数列,所以他和普通的线段树还不同,它的节点还要再套点其他东西
那么,我们要套什么呢?
它不是还要查询最大值吗,那我们就套一个堆
又有问题了,有区间操作,怎么下传懒标?将堆与孩子的堆进行合并——左偏树,想到这里,恭喜你,掉坑里了
左偏树合并是将整个堆都合并过去,他又不只有一个孩子,它和一个孩子合并了,那其他孩子呢?所以左偏树不行
既然传懒标很麻烦,那我们就不穿懒标了呗,这里介绍一个小优化——标记永久化
什么意思呢,就是标记不下放,永远留在当前位置
我们用堆来记录懒标,但是本题中标记永久化的不彻底,还有一种情况需要下传懒标,当堆顶元素与要删除的元素相等时,我们要下放懒标记

代码

定义线段树:

struct tree {
	int len;// 代表区间长度
	ll maxx;// 代表这个区间的最大值
	priority_queue<ll> q;// 大根堆记录并维护懒标
}t[N << 4];

建树操作:

void build(int cur, int l, int r) {// 建树
	t[cur].len = r - l + 1;// 求len
	t[cur].maxx = -1;// 求max
	t[cur].q.push(-1);// -1压堆底
	if(l == r)	return ;// 到达叶子节点
	int mid = (l + r) >> 1;
	build(ls, l, mid);
	build(rs, mid + 1, r);
}

这些与普通线段树没什么差异
接下来就是标记永久化

void addlazy(int cur, ll v) {// 永久化懒标记
	t[cur].q.push(v);// 插入懒标记
	t[cur].maxx = t[cur].maxx > v ? t[cur].maxx : v;
	// 将max值与懒标记比大小
}

更新操作

void pushup(int cur) {
	t[cur].maxx = max(max(t[ls].maxx, t[rs].maxx), t[cur].q.top());
	// 将左右孩子的最大值与懒标记的最大值比大小
}

插入操作

void insert(int cur, int l, int r, int ql, int qr, ll k) {// 插入
	if(ql <= l && r <= qr) {
		addlazy(cur, k);// 永久化标记
		return ;
	}
	int mid = (l + r) >> 1;
	if(ql <= mid)	insert(ls, l, mid, ql, qr, k);
	if(qr > mid)	insert(rs, mid + 1, r, ql, qr, k);
	pushup(cur);// pushup更新
}

查询操作

ll ask(int cur, int l, int r, int ql, int qr) {// 查询最大苦涩值
	if(ql <= l && r <= qr) {
		return t[cur].maxx;// 返回这个区间的最大苦涩值
	}
	ll ans = t[cur].q.top();// 懒标记的最大值
	int mid = (l + r) >> 1;
	if(ql <= mid)	ans = max(ans, ask(ls, l, mid, ql, qr));
	// 左子树的最大苦涩值
	if(qr > mid)	ans = max(ans, ask(rs, mid + 1, r, ql, qr));
	// 右子树的最大苦涩值
	return ans;
}

删除操作

void del(int cur, int l, int r, int ql, int qr, ll k) {
	if(ql <= l && r <= qr && t[cur].maxx < k)	return ;
	// 如果这个区间的最大值都比不过k,那就没有要删除的
	if(t[cur].q.top() == k) {// 最大的懒标记与k相等
		t[cur].q.pop();// 弹出
		pushdown(cur, l, r, ql, qr, k);// 下放懒标记
		if(l == r) {// 到达叶子节点,修改最大值
			t[cur].maxx = t[cur].q.top();
			//叶结点的最大值就是懒标最大值
		}
		else	pushup(cur);//更新最大值
		return ;
	}
	int mid = (l + r) >> 1;
	if(ql <= mid)	del(ls, l, mid, ql, qr, k);
	// 在左子树中删除
	if(qr > mid)	del(rs, mid + 1, r, ql, qr, k);
	// 在右子树中删除
	pushup(cur);
}

完整代码

#include <iostream>
#include <cstdio>
#include <queue>
#define ls (cur << 1)
#define rs (cur << 1 | 1)
typedef long long ll;
using namespace std;
const int N = 2e5 + 5;
int n, m;

struct tree {
	int len;// 代表区间长度
	ll maxx;// 代表这个区间的最大值
	priority_queue<ll> q;// 大根堆记录并维护懒标
}t[N << 4];

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while(ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

void build(int cur, int l, int r) {// 建树
	t[cur].len = r - l + 1;// 求len
	t[cur].maxx = -1;// 求max
	t[cur].q.push(-1);// -1压堆底
	if(l == r)	return ;// 到达叶子节点
	int mid = (l + r) >> 1;
	build(ls, l, mid);
	build(rs, mid + 1, r);
}

void addlazy(int cur, ll v) {// 永久化懒标记
	t[cur].q.push(v);// 插入懒标记
	t[cur].maxx = t[cur].maxx > v ? t[cur].maxx : v;
	// 将max值与懒标记比大小
}

void pushup(int cur) {
	t[cur].maxx = max(max(t[ls].maxx, t[rs].maxx), t[cur].q.top());
	// 将左右孩子的最大值与懒标记的最大值比大小
}

void pushdown(int cur, int l, int r, int ql, int qr, ll k) {
// 把懒标记下放到[ql,qr]以外的其他区间中
	if(ql <= l && r <= qr)	return ;// 永久化标记
	int mid = (l + r) >> 1;
	if(ql > mid) {// 全在右子树上
		addlazy(ls, k);// 永久化标记
		pushdown(rs, mid + 1, r, ql, qr, k);// 下放标记
	}
	else {
		if(qr <= mid) {// 全在左子树上
			addlazy(rs, k);// 永久化标记
			pushdown(ls, l, mid, ql, qr, k);// 下放标记
		}
		else {// 跨左右子树
			pushdown(ls, l, mid, ql, qr, k);
			pushdown(rs, mid + 1, r, ql, qr, k);
		}
	}
	pushup(cur);
}

void insert(int cur, int l, int r, int ql, int qr, ll k) {// 插入
	if(ql <= l && r <= qr) {
		addlazy(cur, k);// 永久化标记
		return ;
	}
	int mid = (l + r) >> 1;
	if(ql <= mid)	insert(ls, l, mid, ql, qr, k);
	if(qr > mid)	insert(rs, mid + 1, r, ql, qr, k);
	pushup(cur);// pushup更新
}

void del(int cur, int l, int r, int ql, int qr, ll k) {
	if(ql <= l && r <= qr && t[cur].maxx < k)	return ;
	// 如果这个区间的最大值都比不过k,那就没有要删除的
	if(t[cur].q.top() == k) {// 最大的懒标记与k相等
		t[cur].q.pop();// 弹出
		pushdown(cur, l, r, ql, qr, k);// 下放懒标记
		if(l == r) {// 到达叶子节点,修改最大值
			t[cur].maxx = t[cur].q.top();
			//叶结点的最大值就是懒标最大值
		}
		else	pushup(cur);//更新最大值
		return ;
	}
	int mid = (l + r) >> 1;
	if(ql <= mid)	del(ls, l, mid, ql, qr, k);
	// 在左子树中删除
	if(qr > mid)	del(rs, mid + 1, r, ql, qr, k);
	// 在右子树中删除
	pushup(cur);
}

ll ask(int cur, int l, int r, int ql, int qr) {// 查询最大苦涩值
	if(ql <= l && r <= qr) {
		return t[cur].maxx;// 返回这个区间的最大苦涩值
	}
	ll ans = t[cur].q.top();// 懒标记的最大值
	int mid = (l + r) >> 1;
	if(ql <= mid)	ans = max(ans, ask(ls, l, mid, ql, qr));
	// 左子树的最大苦涩值
	if(qr > mid)	ans = max(ans, ask(rs, mid + 1, r, ql, qr));
	// 右子树的最大苦涩值
	return ans;
}

int main() {
	t[0].maxx = -1;
	t[0].q.push(-1);
	n = read();
	m = read();
	build(1, 1, n);
	for(int i = 1; i <= m; ++i) {
		int op = read();
		int l, r, k;
		if(op == 1) {
			l = read(), r = read(), k = read();
			insert(1, 1, n, l, r, k);// 插入节点
		}
		if(op == 2) {
			l = read(), r = read();
			k = ask(1, 1, n, l, r);// 找最大苦涩值
			if(k != -1)	del(1, 1, n, l, r, k);
			// 如果有最大苦涩值,删除
		}
		if(op == 3) {
			l = read(), r = read();
			printf("%lld\n", ask(1, 1, n, l, r));// 找最大苦涩值
		}
	}
	return 0;
}
posted @ 2022-08-08 09:24  yi_fan0305  阅读(131)  评论(0编辑  收藏  举报