[Medium] CF1969E Unique Array
2025/2/3约 974 字大约 5 分钟
阅读本题解前,请阅读并注意区分【简要题意】中好的序列和优秀的序列的概念。
简要题意
定义一个序列是好的,当且仅当存在一个数在序列中出现且仅出现一次。定义一个序列是优秀的,当且仅当其所有非空子段都是好的。
给定一个长度为 的序列 ,你可以进行任意次操作,每次操作将 选定一个 修改为任意整数。你需要将 变成优秀的序列,输出最小操作次数。
组数据。。
思路
非常厉害的一道题目,明明每一步都很自然,为什么我想不出来?
首先我们考虑修改一个位置,一定会修改成一个不在序列中出现的数,以消除其影响。所以其实相当于在序列中选择若干个数充当断点,用这些断点分割序列(抛弃断点),则每个分割得到的段都必须是优秀的(由于断点的数是唯一的,所以跨越段的区间,一定是好的)。
根据这个题意转化,我们很容易设计一个贪心,我们从左往右扫,遇到一个点,如果到这个点的前缀不再是优秀的了,就将这个点充当断点。这个贪心的策略正确性几乎是显然的。
由于对于前缀 不再是优秀的,而 是优秀的,所以一定是存在一个右端点为 的区间不是好的。这启发我们研究哪些子段是好的。
对于每个数 ,记其前面第一个与 相同的数为 ,后面第一个与 相同的数为 ,则所有左端点位于 ,右端点位于 的区间都是好的(因为包含了一个出现一次的元素 )。
于是可以设计一个类似扫描线的过程,维护一个数据结构,支持区间加区间查询最小值,一个位置 表示可以以这个点为左端点,那么遇到 就将 增加 ,遇到 就将这个区间减去 ,这是容易实现的。实现时只需要处理出 即可。
时间复杂度 。
代码
#include <bits/stdc++.h>
#define ls (i << 1)
#define rs (i << 1 | 1)
#define mid ((l + r) >> 1)
using namespace std;
const int N = 3e5 + 5;
int n, a[N], bkt[N], pre[N];
int t[N << 2], tag[N << 2];
void mktag(int i, int v){ tag[i] += v, t[i] += v; }
void pushdown(int i){
if(tag[i]) mktag(ls, tag[i]), mktag(rs, tag[i]), tag[i] = 0;
}
void update(int ql, int qr, int v, int i, int l, int r){
if(ql > qr) return;
if(ql <= l && r <= qr) return mktag(i, v);
pushdown(i);
if(ql <= mid) update(ql, qr, v, ls, l, mid);
if(mid < qr) update(ql, qr, v, rs, mid + 1, r);
t[i] = min(t[ls], t[rs]);
}
int query(int ql, int qr, int i, int l, int r){
if(ql > qr) return INT_MAX;
if(ql <= l && r <= qr) return t[i];
pushdown(i);
int res = INT_MAX;
if(ql <= mid) res = min(res, query(ql, qr, ls, l, mid));
if(mid < qr) res = min(res, query(ql, qr, rs, mid + 1, r));
return res;
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=1;i<=n;i++) pre[i] = bkt[a[i]], bkt[a[i]] = i;
int p = 1, ans = 0;
for(int i=1;i<=n;i++){
update(pre[i] + 1, i, 1, 1, 1, n);
update(pre[pre[i]] + 1, pre[i], -1, 1, 1, n);
if(!query(p, i, 1, 1, n)) p = i + 1, ans++;
}
cout << ans << '\n';
}
void clear(){
fill(bkt + 1, bkt + n + 1, 0);
fill(pre + 1, pre + n + 1, 0);
fill(t + 1, t + (n << 2) + 1, 0);
fill(tag + 1, tag + (n << 2) + 1, 0);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t; cin >> t;
while(t--) solve(), clear();
return 0;
}
// Written by xiezheyuan