[COCI2015-2016#3] NEKAMELEONI 题解

前言

题目链接:洛谷

题意简述

你要维护一个序列 \(a_i \in [1, k]\)\(k \leq 50\)),支持:

  1. 单点修改;
  2. 询问最短的包含全部 \(1 \sim k\) 的自区间长度,或报告无解。

题目分析

我想到了两种做法,写题解以加深印象。

方法 \(1\):直接用线段树维护

只有单点修改,尝试用线段树维护分治。考虑如何 pushup

pushup 先继承左右区间的答案,然后考虑经过当前分治中心的区间对答案的贡献。

先考虑暴力。枚举左区间里一个端点 \(l\),那么我们要找到在右区间里找到一个 \(r\),使得所有颜色均在 \(l \sim r\) 中出现。记录选出左半区间的颜色集合为 \(\mathcal{S}\),需要找到右半部分出现颜色集合为 \(\complement_{\lbrace i \rbrace _ {i = 1} ^ {k}} \mathcal{S}\)。因为 \(k\) 的范围很小,我们想到状压,用一个 01 串刻画每种颜色是否出现。那么只用用全集异或一下,再用个桶什么的记一下右半部分的信息即可。

时间复杂度想要正确,那么 pushup 应该和 \(k\) 有关。发现,取按位或的过程是单调不降的,增加到全集最多增加 \(k\) 次,因此,我们只需要记 \(k\) 个按位或发生突变的位置即可。换句话说,你只用关心新的一个颜色出现的位置,而这种位置最多只有 \(k\) 个。

因此,线段树结点里我们只需要记录前缀和后缀的按位或发生突变的位置以及相应值即可。合并的时候,使用双指针即可在 \(\Theta(k)\) 的时间内完成。

时间复杂度:\(\Theta((n+q) k \log n)\),空间复杂度:\(\Theta(nk)\)

方法 \(2\):找性质 + 线段树

我们考虑,一个最优的区间一定满足什么。显然,此时两个端点必定不能往里面缩。说明了什么?说明往里缩的话会有一个颜色没有出现。换句话说,两个端点必定是在区间内仅出现过一次的颜色。不妨只考虑 \(\Theta(n)\) 枚举一端,是其必要不充分条件,要快速找到另一个端点,使这段区间内出现了所有 \(k\) 种颜色。这一步可以用线段树上二分 \(\Theta(\log n)\) 地求出。于是,我们用 \(\Theta(n \log n)\) 的时间搞出了本来 \(\Theta(n ^ 2)\) 的所有区间。

具体地,我们可以用 \(k\)set 维护 \(k\) 种颜色出现的所有位置。特别地,每一个 set 都插入 \(0\)\(n + 1\)。对于某个位置 \(i\),我们找到上一个出现 \(a_i\) 的位置 \(j\),并在二分出一个 \(p \in (j, i]\),使 \([p, i]\) 包含了 \(k\) 种颜色。对于后继同理。

别忘了,我们还要支持修改。显然,我们需要删除包含这个点的信息,再加上修改后的信息。我们不妨枚举 \(k\) 种颜色,然后把包括这个位置的这一段的信息删除。这一步体现在在每一种颜色的 set 里,找到这一个位置前驱后继,并删除答案。维护所有区间的答案可以用可删堆或 multiset。想通了实现起来很简单。

时间复杂度:\(\Theta(n \log n + qk \log n)\),空间复杂度:\(\Theta(n)\),均优于第一种做法。但是,无奈它常数大。

考虑优化?

预处理可以双指针 \(\Theta(n)\) 地搞,不过加到答案堆里有一个 \(\log\)

我们在修改 \(p\) 的时候,不断二分,找到一个位置 \(p'\) 使 \(a_{p'}\)\((p', p]\) 中第一次出现,那么,答案可能是以 \(p'\) 为左端点的一段区间,二分找到右端点并更新到答案里即可。

等等!那这样原先的有些区间不会失效吗?是的,但是与其考虑去删除这些区间,不如在查询的时候再判断。即如果堆顶不合法直接舍弃。

时间复杂度依然是:\(\Theta(n \log n + qk \log n)\),尽管空间变成了 \(\Theta(n + qk)\),但是常数小了很多,比法 \(1\) 快了 \(2\) 秒左右。

代码

方法 \(1\)

代码提交记录

方法 \(2\)

优化前

代码提交记录

优化后

提交记录

// #pragma GCC optimize(3)
// #pragma GCC optimize("Ofast", "inline", "-ffast-math")
// #pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <cstdio>
#include <set>
#include <queue>
using namespace std;

int n, k, m;
long long all;
int val[100010];

struct Segment_Tree {
	#define lson (idx << 1    )
	#define rson (idx << 1 | 1)
	
	struct node {
		int l, r;
		long long val;
	} tree[100010 << 2];
	
	void pushup(int idx) {
		tree[idx].val = tree[lson].val | tree[rson].val;
	}
	
	void build(int idx, int l, int r) {
		tree[idx] = {l, r, 0};
		if (l == r) return tree[idx].val = 1ll << (val[l] - 1), void();
		int mid = (l + r) >> 1;
		build(lson, l, mid);
		build(rson, mid + 1, r);
		pushup(idx);
	}
	
	void modify(int idx, int p, int v) {
		if (tree[idx].l > p || tree[idx].r < p) return;
		if (tree[idx].l == tree[idx].r) return tree[idx].val = 1ll << (v - 1), void();
		modify(lson, p, v), modify(rson, p, v), pushup(idx);
	}
	
	long long query(int idx, int l, int r) {
		if (tree[idx].l > r || tree[idx].r < l) return 0;
		if (l <= tree[idx].l && tree[idx].r <= r) return tree[idx].val;
		return query(lson, l, r) | query(rson, l, r);
	}
	
	int find(int idx, long long d) {
		if (tree[idx].l == tree[idx].r) return tree[idx].l;
		if ((tree[rson].val | d) == all) return find(rson, d);
		return find(lson, d | tree[rson].val);
	}
	
	int queryl(int idx, int l, int r, long long &d) {
		if (tree[idx].l > r || tree[idx].r < l) return -1;
		if (l <= tree[idx].l && tree[idx].r <= r) {
			if ((d | tree[idx].val) != all)
				return d |= tree[idx].val, -1;
			if (tree[idx].l == tree[idx].r)
				return tree[idx].l;
			int res = queryl(lson, l, r, d);
			if (~res) return res;
			return queryl(rson, l, r, d);
		}
		int res = queryl(lson, l, r, d);
		if (~res) return res;
		return queryl(rson, l, r, d);
	}
	
	int find(int idx, int l, int r, long long d) {
		if (tree[idx].l > r || tree[idx].r < l) return -1;
		if (l <= tree[idx].l && tree[idx].r <= r) {
			if ((tree[idx].val | d) == d) return -1;
			if (tree[idx].l == tree[idx].r) return tree[idx].l;
			if ((tree[rson].val | d) ^ d)
				return find(rson, l, r, d);
			return find(lson, l, r, d);
		}
		int res = find(rson, l, r, d);
		return ~res ? res : find(lson, l, r, d);
	}
	
	#undef lson
	#undef rson
} yzh;

inline bool check(int l, int r) {
	return yzh.query(1, l, r) == all;
}

int queryl(int L, int R) {
	long long done = 0;
	return yzh.queryl(1, L, R, done);
}

struct Heap {
	struct node {
		int l, r;
		inline bool friend operator < (const node & a, const node & b) {
			return a.r - a.l > b.r - b.l;
		}
	};
	priority_queue<node> yzh;
	
	inline void push(int l, int r) { yzh.push({l, r}); }
	inline int top() {
		while (!check(yzh.top().l, yzh.top().r)) yzh.pop();
		return yzh.top().r - yzh.top().l + 1;
	}
} ans;

inline void add(int p) {
	ans.push(p, queryl(p, n));
}

inline void modify(int p, int v) {
	val[p] = v, yzh.modify(1, p, v);
	if (yzh.tree[1].val == all) {
		int x = min(p, yzh.find(1, 0));
		long long cur = 1ll << (val[p] - 1);
		add(x);
		while (x > 1) {
			x = yzh.find(1, 1, x - 1, cur);
			if (!~x) return;
			cur |= 1ll << (val[x] - 1), add(x);
		}
	}
}

signed main() {
	fread(buf, 1, MAX, stdin);
	read(n), read(k), read(m), all = (1ll << k) - 1;
	for (int i = 1; i <= n; ++i) read(val[i]);
	yzh.build(1, 1, n);
	for (int l = 1, r = 0, tot = 0; l <= n; ++l) {
		static int cnt[55];
		while (r < n && tot < k) tot += !cnt[val[++r]]++;
		if (!--cnt[val[l]] && r <= n && tot-- == k) ans.push(l, r);
	}
	for (int op, p, v; m--; ) {
		read(op);
		if (op == 1) {
			read(p), read(v), modify(p, v);
		} else {
			if (yzh.tree[1].val == all)
				write(ans.top()), putchar('\n');
			else
				putchar('-'), putchar('1'), putchar('\n');
		}
	}
	fwrite(obuf, 1, o - obuf, stdout);
	return 0;
}
posted @ 2024-08-10 22:04  XuYueming  阅读(6)  评论(0编辑  收藏  举报