首页 > 其他分享 >P4690 [Ynoi2016] 镜中的昆虫

P4690 [Ynoi2016] 镜中的昆虫

时间:2024-03-25 09:11:24浏览次数:30  
标签:Nxt const int Ynoi2016 P4690 修改 MAXN Blk 镜中

P4690 [Ynoi2016] 镜中的昆虫

本质就是 区间修改,区间数颜色

弱化一下,先考虑不带修的情况,也就是 P1972 [SDOI2009] HH的项链

其难点在于 区间颜色的去重,需要想一个办法 让区间内一个颜色只被数一次

我们可以去维护一个 \(Nxt\) 数组,表示 下一个与当前位置颜色相同的位置

若 当前位置 \(i\) 是该颜色出现的 最后一个位置,序列长为 \(N\),则 \(Nxt_i = N + 1\)

容易发现 区间 \([L, R]\) 中 每种颜色 的最后一个位置 \(P_i\) 将满足 \(Pos_{P_i} > R\)

这样每种颜色 恰好对应一个位置,不重不漏

于是我们可以考虑 计数上面这个东西,也就是在 区间中 \(Nxt\) 值大于 \(R\) 的位置个数

转化到平面上 进行 二维数点 即可


然后考虑 单点修改,注意到其实 颜色本身 已经没啥用了

我们关心修改之后对应的 \(Nxt\) 数组的变化

令人高兴的是,单点修改 只会对 修改点 以及 与 修改前 / 后 同色的 前驱点 的 \(Nxt\) 值造成影响

就是 每次 \(Nxt\) 数组只有 \(O(1)\) 的修改,所以 暴力修改,询问时 在线二维数点 即可


回到这道题的 区间修改,感觉就很难做

如果 单点修改 \(O(1)\),区间 \(O(N)\),那这玩意儿 复杂度就炸了,火大

但显然并 没有什么很好的方法可以批量快速修改,于是仔细思考性质

发现区间 \([L, R]\) 修改后,\([L, R - 1]\) 的 \(Nxt\) 都将指向 其对应的下一个位置

就是 \(\forall i \in [L, R - 1], Nxt_i = i + 1\)

而且无论修改成什么颜色这个性质都将存在

换言之,如果我将 \([L, R]\) 的颜色先修改成 \(C_1\),再修改成 \(C_2\)

中间 \([L, R - 1]\) 的 \(Nxt\) 值都 不会改变,于是我们可以考虑 摊还分析

设若初始时 \(\forall i \in [1, N],Nxt_i = i + 1\)

那么我们每次修改时,至多有 \(R\),与 \(L\) 修改前后同色 的 三个点 的 \(Nxt\) 会被改变

于是我们总的修改就不是 \(O(NM)\) 而是 \(O(N + M)\) 的了

从 \(Nxt_i = i + 1\) 的状态变成 输入状态 相当于 修改了 \(O(N)\) 次

于是我们就可以 直接做 \(—— \textsf{z}\color{red}\textsf{hicheng}\)

但是这里 与 \(L\) 修改前后同色的前驱点 还得考虑一下

在我们可以像 单点修改 时 那样记录每个点 前驱和后缀 来找,但是这就意味着 修改常数巨大

所以我们可以 引入一些 \(ODT\) 来维护 每种颜色的区间,同时再用一棵 \(ODT\) 维护整个序列

\(ODT\) 又称 珂朵莉树 \((Chtholly-Tree)\),多用在 维护区间颜色相关的问题

本质上就是一个 std::set,它将 每个相同颜色的段 缩成一个点 用 \(L, R\) 描述

同时可以有一些 附加权值或信息,其 核心操作Split

即 将一个节点(区间) \([L, R]\) 分成 \([L, x - 1],[x, R]\) 两个节点,并返回 后一个节点的指针

一般情况下,我们会 暴力的对树上的区间操作,其复杂度在 随机数据下 为 \(O(N \log\log N)\)

但是也 容易被卡,但是本题中其 均摊复杂度是正确的,不必担心

入门题 CF915E Physical Education Lessons

现在我们将 不用改的部分 忽略后 每次暴力单点修改 就行了

继续 在线二维数点树套树...

不对,注意到(在某次更新之后)这道题的空间限制只有 \(64 ~ MB\)

而 二维树套树 的空间复杂度是 严格 \(O(N \log N)\) 的,过不了一点

但是咱又不想写 \(CDQ\)...

于是,我们直接对 序列分块维护,注意到 修改 \(M\) 次,原序列 \(N\) 个数

一共仅有 \(N + M\) 个 可能权值,于是我们可以对 所有权值离散化 一下

然后对于 序列上每个块,内部使用 值域分块 维护

\(O(\sqrt {N})\) 修改(个数 \(+1\)),\(O(1)\) 查询 后缀和(也就是 大于某个数的个数

虽然分块这玩意儿吧,空间理论上是 \(O(N \sqrt N)\) 的,比 树套树还大

但是这只是理论,我们可以通过 调整块长 获得 更小的空间,然后卡过去

时间换空间,赢!

在时间上,分块 \(O(N \sqrt N)\) 的复杂度在 \(10 ^ 5\) 的数据下不比其余算法 \(O(N \log ^ 2 N)\) 的理论上劣

而且 常数较小,实际上 跑得飞快

#include <bits/stdc++.h>

const int MAXN = 200005;
const int MAXB = 505;
const int RELB = 80;

using namespace std;

int Nxt[MAXN];
int N, M, P, Cnt;

int Blk2[MAXN];

struct Block {
	int BL[MAXB], BR[MAXB];
	int *Blk = Blk2;
	int b;
	short SufA[MAXN], SufB[MAXB];
	
	inline void Init () {
		b = 400;
		for (int i = 1; i <= N + 1; ++ i) {
			Blk[i] = i / b + 1;
			if (Blk[i] != Blk[i - 1]) BL[i / b + 1] = i, BR[i / b] = i - 1;
		} BR[N / b + 1] = N + 1;
	}
	
	inline void Add (const int x) {
		for (int i = 1; i <= Blk[x]; ++ i) ++ SufB[i];
		for (int i = BL[Blk[x]]; i <= x; ++ i) ++ SufA[i];
	}
	
	inline void Del (const int x) {
		for (int i = 1; i <= Blk[x]; ++ i) -- SufB[i];
		for (int i = BL[Blk[x]]; i <= x; ++ i) -- SufA[i];
	}
	
	inline short GetSuf (const int x) {
		return SufB[Blk[x] + 1] + SufA[x];
	}
} B[RELB];

int Blk[MAXN], BL[RELB], BR[RELB];

namespace Chtholly_Tree {
	
	struct Node {
		int L, R, C;
		
		inline bool operator < (const Node &a) const {
			return L < a.L;
		} 
	};
	
	set <Node> S, T[MAXN];
	
	inline auto Split_1 (const int x) {
		if (x > N) return S.end ();
		auto it = -- S.upper_bound ({x, 0, 0});
		if (it -> L == x) return it;
		Node k1 = {it -> L, x - 1, it -> C}, k2 = {x, it -> R, it -> C};
		return S.erase (it), S.insert (k1), S.insert (k2).first;
	}
	
	inline auto Split_2 (const int x, const int c) {
		if (x > N) return T[c].end ();
		auto it = -- T[c].upper_bound ({x, 0, 0});
		if (it -> L == x) return it;
		Node k1 = {it -> L, x - 1, it -> C}, k2 = {x, it -> R, it -> C};
		return T[c].erase (it), T[c].insert (k1), T[c].insert (k2).first;
	}
	
	inline void Change (const int x, const int v) {
		B[Blk[x]].Del (Nxt[x]), Nxt[x] = v, B[Blk[x]].Add (Nxt[x]);
	}
	
	inline void Modify (const int L, const int R, const int x) {
		auto itr = Split_1 (R + 1), itl = Split_1 (L), it = itl; 
		Split_2 (L, itl -> C), Split_2 (R + 1, itr -> C);
		
		for (it = itl; it != itr; ++ it) {
			auto tmp = T[it -> C].lower_bound ({it -> L, 0, 0});
			T[it -> C].erase (tmp);
			tmp = T[it -> C].lower_bound ({it -> L, 0, 0}); auto itx = tmp;
			if (tmp == T[it -> C].end () && T[it -> C].size ()) -- tmp, Change (tmp -> R, N + 1);
			else if (tmp != T[it -> C].begin ()) -- tmp, Change (tmp -> R, itx -> L);

			Change (it -> L, it -> L + 1);
			if (it -> L == it -> R) continue ;
			Change (it -> R, it -> R + 1);
		}
		S.erase (itl, itr), S.insert ({L, R, x}), T[x].insert ({L, R, x});
		
		itl = T[x].lower_bound ({L, 0, 0});
		if (itl != T[x].begin ()) -- itl, Change (itl -> R, L);
		itr = T[x].upper_bound ({R, 0, 0});
		if (itr != T[x].end ()) Change (R, itr -> L);
		else Change (R, N + 1);
	}
	
}

using namespace Chtholly_Tree;

inline int Query (const int L, const int R) {
	int Ret = 0;
	if (Blk[L] == Blk[R]) {
		for (int i = L; i <= R; ++ i) if (Nxt[i] > R) ++ Ret;
	} else {
		for (int i = L; i <= BR[Blk[L]]; ++ i) if (Nxt[i] > R) ++ Ret;
		for (int i = Blk[L] + 1; i < Blk[R]; ++ i) Ret += B[i].GetSuf (R + 1);
		for (int i = BL[Blk[R]]; i <= R; ++ i) if (Nxt[i] > R) ++ Ret;
	}
	return Ret;
}

unordered_map <int, int> Mp;
int Num[MAXN], Val[MAXN], Pos[MAXN], Rnk;
short Opt[MAXN];
int L[MAXN], R[MAXN], x[MAXN];

int main () {
	
	cin >> N >> M, P = N / 78;
	
	for (int i = 1; i <= N; ++ i) cin >> Num[i];
	
	for (int i = 1; i <= M; ++ i) {
		cin >> Opt[i] >> L[i] >> R[i];
		if (Opt[i] == 1) cin >> x[i];
	}
	
	memcpy (Val, Num, sizeof Num), Cnt = N;
	for (int i = 1; i <= M; ++ i) Val[++ Cnt] = x[i];
	sort (Val + 1, Val + Cnt + 1);
	
	for (int i = 1; i <= N; ++ i) {
		Blk[i] = i / P + 1;
		if (Blk[i] != Blk[i - 1])
			BL[i / P + 1] = i, BR[i / P] = i - 1, B[i / P + 1].Init ();
	} BR[N / P + 1] = N;
	 
	for (int i = 1; i <= Cnt; ++ i) Mp[Val[i]] = i;
	
	for (int i = 1; i <= N; ++ i) T[Mp[Num[i]]].insert ({i, i, Mp[Num[i]]}), S.insert ({i, i, Mp[Num[i]]});
	
	for (int i = N; i >= 1; -- i) {
		if (Pos[Mp[Num[i]]]) Nxt[i] = Pos[Mp[Num[i]]], B[Blk[i]].Add (Nxt[i]);
		else Nxt[i] = N + 1, B[Blk[i]].Add (N + 1);
		Pos[Mp[Num[i]]] = i;
	}
	
	for (int i = 1; i <= M; ++ i) {
		if (Opt[i] == 1) Modify (L[i], R[i], Mp[x[i]]);
		if (Opt[i] == 2) cout << Query (L[i], R[i]) << '\n';
	}
	
	return 0;
}

标签:Nxt,const,int,Ynoi2016,P4690,修改,MAXN,Blk,镜中
From: https://www.cnblogs.com/FAKUMARER/p/18093664

相关文章

  • LCR 183. 望远镜中最高的海拔(双端队列)
    科技馆内有一台虚拟观景望远镜,它可以用来观测特定纬度地区的地形情况。该纬度的海拔数据记于数组 heights ,其中 heights[i] 表示对应位置的海拔高度。请找出并返回望远镜视野范围 limit 内,可以观测到的最高海拔值。示例1:输入:heights=[14,2,27,-5,28,13,39],limit=......
  • P4690 [Ynoi2016] 镜中的昆虫 题解
    题目链接:镜中的昆虫经典题了,我们首先回顾下颜色数的常见做法统计:对每个位置维护一个\(pre_i\),表示与当前位置相同的颜色上一次出现位置。那么我们分讨一下。查询\([l,r]\)得到颜色数,对于\(pre_i<l\)的\(i\)点,显然它就是这个区间内\(a_i\)对应颜色出现的第一个位置,我们......
  • P4688 [Ynoi2016] 掉进兔子洞
    题意给定长度为\(n\)的序列\(s\)。有\(m\)个询问,每次询问三个区间,把三个区间中同时出现的数一个一个删掉,问最后三个区间剩下的数的个数和,询问独立。Sol不难发现答案即为求:\(r1-l1+r2-l2+r3-l3+3-siz\)。其中\(siz\)表示三个区间的公共颜色的个数。仔细......
  • [Ynoi2016] 镜中的昆虫
    64MB,1.5s题目描述您正在欣赏galgame的HS,然后游戏崩溃了,于是您只能做数据结构题了:维护一个长为 \(n\) 的序列 \(a_i\),有 \(m\) 次操作。将区间 \([l,r]\) 的值修改为 \(x\)。询问区间 \([l,r]\) 出现了多少种不同的数,也就是说同一个数出现多次只算一个。......
  • [Ynoi2016] 这是我自己的发明(根号分治+分块/莫队)
    题目传送门soltion简单题换根显然可以拆成\(O(1)\)个区间,这里先不管。直接做法是莫队,把双子树拆成\(dfs\)序上的双前缀,可以直接莫队,但是常数比较大。另一种做法是根分,对颜色出现次数分治,大于的求出\(dfs\)序的前缀和即可,小于的因为一共只有\(O(n\sqrtn)\)个点对,所以......
  • luogu P4690 [Ynoi2016] 镜中的昆虫 题解
    P4690[Ynoi2016]镜中的昆虫题解题意维护一个长为 \(n\) 的序列 \(a_i\),有 \(m\) 次操作。将区间 \([l,r]\) 的值修改为 \(x\)。询问区间 \([l,r]\) 出现了多少种不同的数,也就是说同一个数出现多次只算一个。题解感觉这道题还是比较有意思的,像是一堆套路......
  • 镜中
    镜中只要想起一生中后悔的事梅花便落了下来比如看她游泳到河的另一岸比如登上一株松木梯子危险的事固然美丽不如看她骑马归来面颊温暖,羞惭。低下头,回答着皇帝一面镜子永远等候她让她坐到镜中常坐的地方望着窗外,只要想起一生中后悔的事梅花便落满了南山这是我最喜欢的......
  • P4688 [Ynoi2016] 掉进兔子洞
    RE了大约12次以后,SoN3ri告诉我是bitset开小了。那你为什么全RE了啊(?题意是给你一个长度为\(n\)的序列,一共\(m\)次询问,每次询问包含三个区间,求三个区间内相同的数去掉后剩下的数的个数。做完了小清新人渣的本愿,看啥都是bitset+莫队,这题我也是一开始打了一个莫队+bitset,但是......
  • [Ynoi2016] 掉进兔子洞
    \(\text{Solution}\)莫队配合\(\text{bitset}\)发现答案困难的部分在于同一个数在三个区间出现次数的最小值考虑强行拆开看,用莫队处理出每个区间每个数的出现次数,这个......
  • [Ynoi2016] 炸脖龙 I
    题目传送门已经能过hack,原因:做快速幂的时候需要微判一下边界。很好奇lxl为什么不卡显然区间加可用线段树做。然后操作二用扩展欧拉定理,每个\(p\)最多递归\(\log\)......