P12568 An Array and Range Additions 题解
Chenhaoxuan · · 题解
P12568 [UOI 2023] An Array and Range Additions
题目大意:
给定一个长度为
首先我们先考虑本题的弱化版本:你需要保证 选择的区间两两不交。
这时问题变成了一个简单的贪心。我们从左至右扫描整个序列,每次遇到一个重复的数字,就把序列从这里断开,然后新开一段。最后,我们保留第一段不变,后面每一段加上一个互不相同的数字。容易证明最优性。
如果区间可以重叠呢?我们先钦定 序列中的所有数字必须至少改变一次,考虑如下
容易发现如下的操作方案:
其中
考虑如下
下面是一种合理的操作方案:
其中
由此推广,我们可以得到如下一般结论:
-
对于
n 次操作,我们最多让2n-1 个区间变得不同。- 具体实现上,让每次操作依次覆盖
[1,n],[2,n+1],\cdots, [n,2n-1] 这些区间即可。
- 具体实现上,让每次操作依次覆盖
-
对于
n 个区间,我们至少进行\left\lfloor\dfrac{n}{2}\right\rfloor+1 次操作才能让它们两两不同。
现在考虑更一般的情形。我们可能会面对答案由若干段的改变构成的情况。下面是一个例子。
| 原序列 | |||||||||
|---|---|---|---|---|---|---|---|---|---|
| 答案序列 |
这样的答案很难求解。但我们发现:我们可以把两个相邻的两个操作区间合并,如下所示:
| 原序列 | |||||||||
|---|---|---|---|---|---|---|---|---|---|
| 新答案序列 |
把中间的无关数字也“带上”操作,容易发现这样的构造也符合要求。于是存在一种合法的构造方案,只有最前面和最后面的一些位置不改变,中间的数字都要改变。
回到本题。我们首先预处理出 每一个点右侧第一次出现重复数字的位置,记为
然后,我们对于每一个合法的前缀
剩下的就只有求出
而上面的跳跃操作是可以倍增优化的,于是我们可以在
注意特判不需要操作的情形。
AC 代码:
int n, a[maxn], cnt;
int nxt[maxn][20], ans = 0x3f3f3f3f;
map<int, int> mp;
int query(int l, int r) {
int res = 1;
for (int j = 19; j >= 0; j--) {
if (nxt[l][j] <= r) l = nxt[l][j], res += (1 << j);
}
return res / 2 + 1;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
nxt[n + 1][0] = n + 1;
for (int i = n; i >= 1; i--) {
nxt[i][0] = nxt[i + 1][0];
if (mp[a[i]]) nxt[i][0] = min(nxt[i][0], mp[a[i]]);
mp[a[i]] = i;
}
for (int i = 1; i <= 19; i++) {
for (int j = 1; j <= n + 1; j++)
nxt[j][i] = nxt[nxt[j][i - 1]][i - 1];
}
mp.clear(); int l, r;
for (r = n; r >= 1; r--) {
if (!mp[a[r]]) mp[a[r]]++;
else break;
}
if (!r) return 0 * puts("0");
ans = min(ans, query(1, r)); r++;
for (l = 1; l <= n; l++) {
while (mp[a[l]] && r <= n) {
mp[a[r]]--; r++;
}
if (mp[a[l]]) break;
mp[a[l]] = 1;
ans = min(ans, query(l + 1, r - 1));
}
printf("%d", ans);
return 0;
}