题解:CF2115F1 Gellyfish and Lycoris Radiata (Easy Version)

节选自:Codeforces Round 1028 (Div. 1)

这题非常好玩,也非常达芬,我做了 \(1\) 天半才过。

我们考虑到问题是在线,因此可以想到支持在线的操作分块(虽然我没想到),每 \(\sqrt q\) 个操作分成一个操作块。考虑到一次操作最多会使序列新增一个颜色段(颜色段内的所有集合都是一样的),因此一个操作块最多会使一个序列分成 \(\mathcal O(\sqrt q)\) 个颜色段,对于每个颜色段 \(i = [l_i, r_i]\),我们维护一个队列 \(q_i\) 表示段内的集合,\(rev_i\) 表示该段是否翻转,\([rl_i, rr_i]\) 表示这个颜色段对应原序列中的哪一段。

此时 \(1\) 操作和 \(2\) 操作比较好维护。我们找到所有需要插入或翻转的颜色段,如果 \(r\) 正好在一个颜色段 \(i\) 中间,我们就需要将该颜色段分裂。注意此时 \([rl_i, rr_i]\) 的分裂和 \(rev_i\) 有关。假设我们要将颜色段 \(i = [6, 10]\)\(7\) 处分裂,\(i\) 对应的原序列中的区间为 \([1, 5]\)。如果 \(rev_i = 0\),那么直接将 \([1, 5]\) 分裂成 \([1, 2]\)\([3, 5]\) 即可;否则就应该分裂成 \([4, 5]\)\([1, 3]\)。队列 \(q_i\) 就直接复制,时间复杂度将在后文分析。

操作 \(3\) 不好维护,但是我们可以延迟删除。如果 \(x\) 小于等于当前操作数,就将 \(x\) 标记成删除。不过此时队列还需要维护一个 \(be_i\),表示最小的没被删除的数字。由于每次被插入集合的都是当前操作编号,而编号是单增的,因此 \(be_i\) 也是最靠前的没被删除的数字。

现在来处理查询操作。对于一个位置 \(p\),我们可以二分查找出当前操作块中包含 \(p\) 的颜色段 \(i\),然后遍历 \(i\) 的队列 \(q_i\),如果枚举到的数字没被删除,那么根据刚才的推导,这一定是这个队列中最小的元素,用它更新答案即可;否则就将 \(be_i\)\(1\)。由于我们记录了 \([rl_i, rr_i]\),因此我们知道 \(p\) 在经过当前操作块之前所处的位置 \(p'\),也就是 \(p\) 在上一个操作块中所处的位置,因此我们不断往下查找即可。

现在来分析时间复杂度。先来考虑修改操作,我们发现一个操作块中最多会被插入 \(\mathcal O(\sqrt q)\) 个数字,而最多只会分裂 \(\mathcal O(\sqrt q)\) 次,每次都会将这 \(\mathcal O(\sqrt q)\) 个数字复制一遍,因此一个操作块内操作的时间复杂度是 \(\mathcal O(q)\),这部分的总时间复杂度就是 \(\mathcal O(q \sqrt q)\)。而查询时,由于我们记录了 \(be_i\),而 \(be_i\) 是单调不降的,因此每个颜色段只会被扫一遍,而颜色段的数量长度和是 \(\mathcal O(n \sqrt q)\) 的。加上二分查找的时间复杂度,这一部分的时间复杂度就是 \(\mathcal O(n \sqrt q \log \sqrt q)\),总时间复杂度就是 \(\mathcal O(q \sqrt q + n \sqrt q \log \sqrt q)\)

完整代码:

点击查看代码
#include <bits/stdc++.h>
using namespace std;
inline int read(){
	int x = 0; bool f(0); char ch = getchar();
	while(!isdigit(ch)) f = (ch == '-'), ch = getchar();
	while(isdigit(ch)) x = (x<<3) + (x<<1) + (ch^48), ch = getchar();
	return f ? -x : x;
}
inline void write(int x){
	if(x < 0) x = -x, putchar('-'); 
	static short Stack[64], top(0);
	do Stack[++ top] = x % 10, x /= 10; while(x);
	while(top) putchar(Stack[top--] | 48);
}
const int N = 2e5 + 9, B = 319, INF = 1e9 + 9;
struct Segment{
	int l, r, rl, rr;
	inline bool operator < (const Segment &a) const{
		return l < a.l;
	}
} t[N];
struct Node{
	int pre, nex;
} e[N];
int head[N];
inline void add(int id, int x){//在 id 后面插入 x
	e[x].nex = e[id].nex;
	e[x].pre = id;
	e[id].nex = x;
}
inline void del(int id){
	e[e[id].nex].pre = e[id].pre;
	e[e[id].pre].nex = e[id].nex;
}
int era[N], rev[N], q[N][B], be[N], ed[N], n, qq, tot = 1, cnt, num = 1, lastans;
void split(int i, int b){
	tot++;
	for(int j = be[i]; j <= ed[i]; j++)
		q[tot][++ed[tot]] = q[i][j];
	rev[tot] = rev[i];
	int tmp1, tmp2, tmp3, tmp4;
	if(rev[i]){
		tmp1 = t[i].rr - (b - t[i].l + 1) + 1;
		tmp2 = t[i].rr;
		tmp3 = t[i].rl;
		tmp4 = t[i].rr - (b - t[i].l + 1);
	} else {
		tmp1 = t[i].rl;
		tmp2 = t[i].rl + (b - t[i].l + 1) - 1;
		tmp3 = t[i].rl + (b - t[i].l + 1);
		tmp4 = t[i].rr;
	}
	t[tot] = Segment{b + 1, t[i].r, tmp3, tmp4};
	t[i].r = b;
	t[i].rl = tmp1;
	t[i].rr = tmp2;
	add(i, tot);
}
int st[N][B], sum[B];
vector <int> vec;
int main(){
	for(int i = 1; i < N; i++)
		be[i] = 1;
    n = read();
	qq = read();
	int k = sqrt(qq), qqq = qq;
	t[tot]= Segment{1, n, 1, n};
	e[tot].pre = 0;
	e[0].pre = -1;
	e[0].nex = tot;
	e[tot].nex = 2 * n;
	e[2 * n].pre = tot;
	e[2 * n].nex = -1;
	head[1] = tot;
	while(qqq--){
		cnt++;
		int a, b, c;
		a = read();
		b = read();
		c = read();
		if(a == 1 || a == 2)
			b = (b + lastans - 1) % n + 1;
		else
			b = (b + lastans - 1) % qq + 1;
		c = (c + lastans - 1) % n + 1;
		if(a == 1){
			int he = 0;
			for(int i = head[num]; i != 2 * n; i = e[i].nex){
				he += t[i].r - t[i].l + 1;
				if(he == b){
					q[i][++ed[i]] = cnt;
					break;
				}
				if(he > b){
					split(i, b);
					q[i][++ed[i]] = cnt;
					break;
				}
				q[i][++ed[i]] = cnt;
			}
		} else if(a == 2){
			int he = 0;
			vec.clear();
			for(int i = head[num]; i != 2 * n; i = e[i].nex){
				he += t[i].r - t[i].l + 1;
				if(he == b){
					vec.push_back(i);
					del(i);
					break;
				}
				if(he > b){
					split(i, b);
					vec.push_back(i);
					del(i);
					break;
				}
				vec.push_back(i);
				del(i);
			}
			for(int i = 0; i <= (int)vec.size() - 1; i++){
				int L = t[vec[i]].l, R = t[vec[i]].r;
				t[vec[i]].l = b - R + 1;
				t[vec[i]].r = b - L + 1;
				add(0, vec[i]);
				rev[vec[i]] ^= 1;
			}
			head[num] = vec[vec.size() - 1];
		} else if(a == 3){
			if(b <= cnt)
				era[b] = true;
		}
		sum[num] = 0;
		for(int i = head[num]; i != 2 * n; i = e[i].nex)
			st[num][++sum[num]] = i;
		int ans = INF;
		for(int i = num; i >= 1; i--){
			int L = 1, R = sum[i], res = 0, j;
			while(L <= R){
				int mid = (L + R) >> 1;
				if(t[st[i][mid]].l <= c)
					L = mid + 1, res = mid;
				else
					R = mid - 1;
			}
			j = st[i][res];
			for(int l = be[j]; l <= ed[j]; l++){
				if(!era[q[j][l]]){
					ans = min(ans, q[j][l]);
					break;
				} else
					be[j]++;
			}
			c = t[j].rl + (c - t[j].l + 1) - 1;
			if(rev[j])
				c = t[j].rl + t[j].rr - c;
		}
		if(ans == INF)
			ans = 0;
		write(lastans = ans);
		putchar('\n');
		if(cnt % k == 0){
			sum[num] = 0;
			for(int i = head[num]; i != 2 * n; i = e[i].nex)
				st[num][++sum[num]] = i;
			num++, tot++;
			t[tot] = Segment{1, n, 1, n};
			e[tot].pre = 0;
			e[0].pre = -1;
			e[0].nex = tot;
			e[tot].nex = 2 * n;
			e[2 * n].pre = tot;
			e[2 * n].nex = -1;
			head[num] = tot;
		}
	}
    return 0;
}
posted @ 2025-10-23 18:58  Orange_new  阅读(10)  评论(1)    收藏  举报