首页 > 其他分享 >[COCI2015-2016#3] NEKAMELEONI 题解

[COCI2015-2016#3] NEKAMELEONI 题解

时间:2024-08-10 22:06:22浏览次数:13  
标签:log idx int 题解 COCI2015 tree 区间 Theta 2016

前言

题目链接:洛谷

题意简述

你要维护一个序列 \(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;
}

标签:log,idx,int,题解,COCI2015,tree,区间,Theta,2016
From: https://www.cnblogs.com/XuYueming/p/18345966

相关文章

  • AtCoder Beginner Contest 366 C,D题解
    C-BallsandBagQuery题解题意没什么好说的,给出q次查询,进行求解思路很简单的一道题,但这篇题解的作用是引出unordered_set,这个东西的作用类似set,但没有排序,相当于哈希。unordered_set有几种操作,接下来介绍三种insert,没什么可说的,普通的插入erase,进行弹出size,返回大......
  • ABC 366 题解
    AtCoderBeginnerContest366题解:\(Problem\hspace{2mm}A-Election\hspace{2mm}2\)题目链接opinion:很显然,当一个人的票数大于等于\(\lceil\frac{n}{2}\rceil\)时此人一定当选。(或可理解为投票结果一定固定。)依次判断两人即可。code:#include<bits/stdc++......
  • CF1674G Remove Directed Edges 题解
    CF1674G给出一个\(n\)点\(m\)边的有向无环图,你需要从中移除一些边,使得对于每一个点,其入度减少(如果原来有入边),出度也减少(如果原来有出边)。当删完边以后,如果有一个点集,满足对于任两点\((i,j)\)可以从\(i\)走到\(j\)或可以从\(j\)走到\(i\),那就称其为可爱的。现在要......
  • CF1863E Speedrun 题解
    CF1863E你在玩一个游戏,要完成\(n\)个任务。其中对于每个任务\(i\),它只能在某一天的第\(h_i\)时刻完成。游戏每天有\(k\)个小时,分别编号为\(0,1,...k-1\)。给出\(m\)对任务间的依赖关系,\((a_i,b_i)\)表示\(a_i\)必须比\(b_i\)先完成。保证依赖关系不形成环。完......
  • 洛谷 P10852 Awaken——题解
    洛谷P10852题解传送锚点摸鱼环节【MX-X2-T1】「CfzRound4」Awaken题目背景能否等到梦醒了的时候。题目描述月做了一个梦。在梦中,她拿到了一个长度为\(n\)的整数序列\(a_1,\ldots,a_n\),其中\(\bm{n\ge5}\)。梦醒了。月忘记了这个序列中的一部分元素,留下了空白......
  • P4933 大师 题解
    题目传送门思路动态规划看到题目就想到了最长上升子序列。类比最长上升子序列,发现多了一个要求,可以多开一维。设\(f_{i,j}\)表示以\(i\)为结尾,\(j\)为公差的序列的长度(点的个数$-1$),那么答案就为\[\sum_{i=1}^{n}\sum_{j=-\max(v)}^{\max(v)}f_{i,j}\]看上去......
  • ABC201E Xor Distances 题解
    ABC201EXorDistances题解题目大意给定一个带权树,求树上每两点的简单路径上的边权的异或和的和。形式化的,定义\(dis(i,j)\)为\(i\)到\(j\)的简单路径上的边权的异或和,求\(\large\sum\limits_{i=1}^n\sum\limits_{j=i+1}^n\text{dis}(i,j)\)。Solve令\(\largef(u)=......
  • 洛谷 P1127 词链——题解
    洛谷P1127题解传送锚点摸鱼环节词链题目描述如果单词\(X\)的末字母与单词\(Y\)的首字母相同,则\(X\)与\(Y\)可以相连成\(X.Y\)。(注意:\(X\)、\(Y\)之间是英文的句号.)。例如,单词dog与单词gopher,则dog与gopher可以相连成dog.gopher。另外还有一些例子:dog......
  • CF1155C 题解
    题目传送门题目大意:给定一个长度为\(n\)的单增序列\(a\)和一个长度为\(m\)的序列\(b\),询问是否存在一个正整数\(y\)使得\(a_1\equiva_2\equiv\cdots\equiva_n\equivy\space(\bmod\spacep)\),且\(p\)在序列\(b\)中出现过。思路:将条件转化一下,得:是否存在一个......
  • 08-08 题解
    08-08题解地址A-CF1420Eluogu翻译更正ifhegivesnomorethatkorders对于至多k次操作,题面没有翻译出来思路怎么算贡献?贡献(被保护)出现在「处在任意两个不同的0的连续段的守卫」之间,而处于同一连续段的守卫之间没有贡献设一共\(cnt\)个\(0\),每个连续......