题解:P14175 【MX-X23-T5】向死存魏

题意

给定长度为 \(n\) 的序列 \(a\) 和值域 \(V\)。有 \(m\) 次操作:

  1. 给定 \(l,r,x\),将 \(a[l,r]\)\(=x\) 的数改为 \(0\)
  2. 给定 \(x\),在序列末尾添加 \(x\)
  3. 给定 \(l\),查询最小的 \(r\) 使得 \(a[l,r]\) 中包含了 \([1,V]\) 中的所有数,或报告无解。

\(1\leq n,m,V\leq 5\times 10^5\)\(1\leq x,a_i\leq V\)

题解

不是很难的题,击杀了。

下面视 \(n,m,V\) 同阶。

考虑对于每个值 \(x\),把序列中所有 \(=x\) 的下标取出来,记作 \(p_1,p_2,\cdots,p_k\),特别地,令 \(p_0=0\)。考察每个 \(1\leq i\leq k\),我们把 \([p_{i-1}+1,p_i]\) 视作一个权值为 \(p_i\) 的线段。显然每个点最多只会被一个值对应的线段覆盖一次,因此对于每次查询,我们首先判断这个点是否被覆盖了恰好 \(V\) 次,若不是则无解,否则答案就是所有覆盖这个点的线段中权值的最大值。

每个点的覆盖次数容易用 BIT 维护,对于权值的最大值,需要支持区间 \(\operatorname{chkmax}\) 和撤销某次 \(\operatorname{chkmax}\) 的效果,我们开一棵线段树,在每个节点上维护一个 multiset,这样每次 \(\operatorname{chkmax}\) 或撤销在对应节点的 multiset 上增删即可,单点查询就返回根到叶子的路径上的 multiset 的最大值。对于每种值,我们用 set 维护其下标序列,那么操作 \(1,2\) 就对应增删区间即可。显然操作 \(1\) 直接遍历区间内 \(=x\) 的位置均摊下来是 \(\mathcal{O}(n\log{n})\) 的。

这样我们就得到了一个 \(\mathcal{O}(n\log^2{n})\) 的做法。可以获得 \(60\) 分。

考虑优化,瓶颈在于线段树的部分。不难注意到对于每种值,线段的权值是从左到右单调递增的,所以对于每个线段 \([l,r]\),我们直接对 \([l,n]\) 做后缀 \(\operatorname{chkmin}\) 也是对的。而对于撤销,可以发现我们总是删除一段区间里的若干相邻线段 \([l_i,r_i]\),然后插入新线段 \([L,R]\),并且因为这些被删除的线段都被 \([L,R]\) 包含,所以我们直接把新线段的影响覆盖上去,就可以撤销掉被删除线段的影响了。这样只需要使用另一棵 BIT 支持后缀 \(\operatorname{chkmin}\) 和单点查询,时间复杂度就优化到了 \(\mathcal{O}(n\log{n})\)

代码

#include <bits/stdc++.h>

using namespace std;

#define lowbit(x) ((x) & -(x))
typedef long long ll;
typedef pair<int, int> pii;
const int N = 5e5 + 5;

template<typename T> inline void chk_min(T &x, T y) { x = min(x, y); }
template<typename T> inline void chk_max(T &x, T y) { x = max(x, y); }

int n, m, v, len, a[N];
set<int> pos[N];

struct BIT {
	int c[N << 1];
	int query(int x) {
		int res = 0;
		for (; x; x -= lowbit(x)) res += c[x];
		return res;
	}
	void add(int x, int v) { for (; x <= n + m; x += lowbit(x)) c[x] += v;}
} ft;
struct BIT2 {
	int c[N << 1];
	int query(int x) {
		int res = 0;
		for (; x; x -= lowbit(x)) chk_max(res, c[x]);
		return res;
	}
	void upd(int x, int v) { for (; x <= n + m; x += lowbit(x)) chk_max(c[x], v); }
} ft2;

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n >> m >> v;
    for (int i = 1; i <= v; ++i) pos[i].insert(0);
    for (int i = 1; i <= n; ++i) {
    	cin >> a[i];
    	int prv = *prev(pos[a[i]].end());
    	ft.add(prv + 1, 1), ft.add(i + 1, -1), ft2.upd(prv + 1, i);
    	pos[a[i]].insert(i);
    }
    len = n;
    for (int i = 1; i <= m; ++i) {
    	int tp; cin >> tp;
    	if (tp == 1) {
    		int l, r, x; cin >> l >> r >> x;
    		auto it1 = pos[x].lower_bound(l), it2 = pos[x].upper_bound(r);
    		for (auto it = it1; it != it2; ++it) {
    			int L = (*prev(it)) + 1, R = *it;
    			ft.add(L, -1), ft.add(R + 1, 1);
    		}
    		if (it2 != pos[x].end()) {
    			int L = (*prev(it2)) + 1, R = *it2;
    			ft.add(L, -1), ft.add(R + 1, 1);
    		}
    		pos[x].erase(it1, it2);
    		it2 = pos[x].upper_bound(r);
    		if (it2 != pos[x].end()) {
    			int L = (*prev(it2)) + 1, R = *it2;
    			ft.add(L, 1), ft.add(R + 1, -1), ft2.upd(L, R);
    		}
    	} else if (tp == 2) {
    		int x; cin >> x;
    		int prv = *prev(pos[x].end()); ++len;
    		ft.add(prv + 1, 1), ft.add(len + 1, -1), ft2.upd(prv + 1, len); 
    		pos[x].insert(len);
    	} else {
    		int l, ans; cin >> l;
    		if (ft.query(l) < v) ans = -1;
    		else ans = ft2.query(l);
    		cout << ans << endl;
    	}
    }
    return 0;
}
posted @ 2025-10-20 22:09  P2441M  阅读(14)  评论(0)    收藏  举报