E. Unique Array
You are given an integer array $a$ of length $n$. A subarray of $a$ is one of its contiguous subsequences (i. e. an array $[a_l, a_{l+1}, \dots, a_r]$ for some integers $l$ and $r$ such that $1 \le l < r \le n$). Let's call a subarray unique if there is an integer that occurs exactly once in the subarray.
You can perform the following operation any number of times (possibly zero): choose an element of the array and replace it with any integer.
Your task is to calculate the minimum number of aforementioned operation in order for all the subarrays of the array $a$ to be unique.
Input
The first line contains a single integer $t$ ($1 \le t \le 10^4$) — the number of test cases.
The first line of each test case contains a single integer $n$ ($1 \le n \le 3 \cdot 10^5$).
The second line contains $n$ integers $a_1, a_2, \dots, a_n$ ($1 \le a_i \le n$).
Additional constraint on the input: the sum of $n$ over all test cases doesn't exceed $3 \cdot 10^5$.
Output
For each test case, print a single integer — the minimum number of aforementioned operation in order for all the subarrays of the array $a$ to be unique.
Example
Input
4
3
2 1 2
4
4 4 4 4
5
3 1 2 1 2
5
1 3 2 1 2
Output
0
2
1
0
Note
In the second test case, you can replace the $1$-st and the $3$-rd element, for example, like this: $[3, 4, 1, 4]$.
In the third test case, you can replace the $4$-th element, for example, like this: $[3, 1, 2, 3, 2]$.
解题思路
参考的官方题解,做法挺难想到的。
首先如果要修改某个元素,显然应该修改成当前数组中不存在的数值,这样修改后,所有包含该元素的子数组必然是 unique 的。对此问题转换成了考虑所有不满足 unique 的子数组,最少需要修改多少个元素,使得这些子数组都至少包含一个被修改的元素。
这就是一个经典的贪心问题:区间选点。即给定若干个区间,问最少选择多少个点使得每个区间内至少包含一个选出的点。做法是先对所有区间按右端点从小到大排序,记上一次选择的点的位置为 $j$(初始时置为 $-\infty$),依次枚举每个区间 $[l_i, r_i]$,如果 $j < l_i$ ,则选择点 $r_i$ 并更新 $j \gets r_i$,答案加 $1$。
在本题中我们可以按照区间的右端点将不满足 unique 的子数组进行分类,依次遍历数组 $a$ 中的每个元素并维护上一个被修改元素的下标 $j$。当枚举到 $a_i$,找到非 unique 的子数组 $a_l \sim a_i$ 的最大下标 $l$,如果 $l > j$,那么修改 $a_i$ 并更新 $j \gets i$。为什么不考虑非 unique 的子数组 $a_{l'} \sim a_i$ $(l' < l)$ 呢?这是因为如果 $l \leq j$,则有 $l' < l < j$,即所有以 $i$ 为右端点的非 unique 子数组都包含了被修改的 $a_j$。否则 $l > j$,我们必须要在 $[l,i]$ 中选择一个元素 ($a_i$) 修改,此时所有以 $i$ 为右端点的非 unique 子数组也都包含了被修改的 $a_i$。
现在的问题是对于每个 $i$,如何快速找到上述提到的 $l$?暴力的做法就是从 $i$ 往左枚举,并维护每个数值出现的次数,当发现到达某个位置时数值都出现了至少两次,那么这个位置就是 $l$。我们可以用支持区间加和查询区间最小值的线段树来加快这个过程。首先我们用另外一种方式去模拟上述过程,开一个数组 $b$,对于 $a_1 \sim a_i$ 出现的每个数值,在 $b$ 的该数值出现的最后一个位置处标记为 $1$,倒数第二个位置标记为 $-1$,其他的位置标记为 $0$。对 $b$ 求后缀和,即 $s_k = \sum\limits_{u=k}^{i}{b_u}$,如果在 $s_j \sim s_i$ 中存在某个 $s_k = 0$,说明必然有 $l > j$,此时就要修改 $a_i$。
不妨假设在枚举完 $a_{i-1}$ 后维护出了 $s_1 \sim s_{i-1}$,此时的 $s_k = \sum\limits_{u=k}^{i-1}{b_u}$。现在枚举到 $a_i$,则需要将每个 $s_k$ 更新成 $s_k = \sum\limits_{u=k}^{i}{b_u}$。具体做法是找到数值 $a_i$ 的倒数第 $2$ 个位置 $x$ 和倒数第 $3$ 个位置 $y$,然后令 $b_y$ 从 $-1$ 变成 $0$;$b_x$ 从 $1$ 变成 $-1$;$b_i$ 变成 $1$。对应到 $s_j$ 的更新就是让 $s_1 \sim s_y$ 都加上 $1$;$s_1 \sim s_x$ 都加上 $-2$;$s_1 \sim s_i$ 都加上 $1$。然后判断 $s_j \sim s_i$ 中的最小值是否大于 $0$。因此我们需要用线段树来维护这个 $s$ 数组。
AC 代码如下,时间复杂度为 $O(n \log{n})$:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3e5 + 5;
int a[N];
vector<int> p[N];
struct Node {
int l, r, mn, add;
}tr[N * 4];
void build(int u, int l, int r) {
tr[u] = {l, r, 0, 0};
if (l != r) {
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
}
void pushdown(int u) {
if (tr[u].add) {
tr[u << 1].mn += tr[u].add;
tr[u << 1].add += tr[u].add;
tr[u << 1 | 1].mn += tr[u].add;
tr[u << 1 | 1].add += tr[u].add;
tr[u].add = 0;
}
}
void modify(int u, int l, int r, int c) {
if (tr[u].l >= l && tr[u].r <= r) {
tr[u].mn += c;
tr[u].add += c;
}
else {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, c);
if (r >= mid + 1) modify(u << 1 | 1, l, r, c);
tr[u].mn = min(tr[u << 1].mn, tr[u << 1 | 1].mn);
}
}
int query(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) return tr[u].mn;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) return query(u << 1, l, r);
if (l >= mid + 1) return query(u << 1 | 1, l, r);
return min(query(u << 1, l, r), query(u << 1 | 1, l, r));
}
void solve() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
p[i].clear();
}
build(1, 1, n);
int ret = 0;
for (int i = 1, j = 0; i <= n; i++) {
p[a[i]].push_back(i);
int sz = p[a[i]].size();
if (sz >= 3) modify(1, 1, p[a[i]][sz - 3], 1);
if (sz >= 2) modify(1, 1, p[a[i]][sz - 2], -2);
modify(1, 1, p[a[i]][sz - 1], 1);
if (!query(1, j + 1, i)) {
j = i;
ret++;
}
}
printf("%d\n", ret);
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}
参考资料
Educational Codeforces Round 165 Editorial:https://codeforces.com/blog/entry/129022
标签:unique,int,修改,le,数组,Array,Unique,sim From: https://www.cnblogs.com/onlyblues/p/18180900