[COCI2015-2016#3] NEKAMELEONI 題解

XuYueming發表於2024-08-10

前言

題目連結:洛谷

題意簡述

你要維護一個序列 \(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})/* , cout << "push " << l << '~' << r << endl */; }
	inline int top() {
		while (!check(yzh.top().l, yzh.top().r)) /* cout << "pop " << yzh.top().l << '~' << yzh.top().r << endl,  */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;
}