P3411 序列变换 题解
自己做不出来,看现在题解区的题解讲的都不咋清楚。懂了之后来为后人铺路。而且我的马蜂比较好看
我能看懂这道题,主要是依靠了这篇题解的帮助。
首先我们只关注数的相对关系,所以可以离散化。注意到值域 \(10^6\),用数组离散化。
这道题可以用贪心做。(有一些定义先往下看)
定义一个无缝子序列:一个子序列中最小值为 \(mn\),最大值为 \(mx\),且原数组中所有值在 \([mn,mx]\) 中的数全部在子序列中,再并且这个子序列单调不减,称这个子序列是 “无缝的”。
例如原序列离散化后是 1 4 4 2 3 7 5 4 5 6
,那么子序列 5 5 6
是无缝的;子序列 4 4 4 6
不是无缝的,因为 \(5\) 没有包含在子序列中;子序列 4 4 5 4 5 6
也不是无缝的,因为不单调不减。
再定义一个无缝子序列的拓展长度:假设一个无缝子序列的最小值为 \(mn\),最大值为 \(mx\),长度为 \(len\)。第一个 \(mn\) 左边有 \(cnt1\) 个 \(mn-1\)。最后一个 \(mx\) 右边有 \(cnt2\) 个 \(mx+1\)。称这个无缝子序列的拓展长度为 \(len+cnt1+cnt2\)。
现在抛出贪心的结论:答案就是 \(n-x\),\(x\) 是最大的 “无缝子序列拓展长度”。注意这里表示的不是 “最长的无缝子序列” 的拓展长度。
为什么呢?首先我们证明存在一组解,可以构造出 \(n-x\) 次操作。
假设无缝子序列 \(\{num\}\) 使得它的拓展长度为 \(x\)。
那就可以固定 \(\{num\}\) 中所有数和所有计算拓展长度时统计到的 \(mn-1\) 和 \(mx+1\)。这些数不进行操作。
剩下所有数,分成两拨:小于 \(mn\) 的和大于 \(mx\) 的。
将小于 \(mn\) 的数从大到小排序,依次将每个数放在开头;将大于 \(mx\) 的数从小到大排序,依次将每个数放在结尾。
这样每个没固定的数都使用了一次操作,共 \(n-x\) 次操作使得整个序列排好了序。
下面证明不存在比 \(n-x\) 还小的解。假设更小的解是 \(k\)。
那么说明至少有 \(n-k\) 个数没有进行操作。这些没进行操作的数一定形成一个 “无缝子序列拓展” 的形式。因为假如它中间有数没有出现,一定需要将外界的数移到中间来,这需要移动这 \(n-k\) 个数;假如它不是单调不减,需要重新排序,也需要移动这些数。
综上所述,最终的答案就是 \(n-x\)。
那具体怎么求呢?对每个位置求出 \(nxt\) 和 \(pre\) 表示后一个/前一个和它相等的数的所在位置。再对每一种数值求出 \(l,r\) 表示这种数值最左边的数和最右边的数的位置。还有每种数值的出现次数 \(cnt\)。
一个无缝子序列的充要条件是:对于任意 \(x<y\) 且 \(x,y\) 属于无缝子序列,\(r_x<l_y\)。
发现如果 \(r_x<l_y,r_y<l_z\) 则 \(r_x<l_z\)。
那么可以从小到大遍历每种数值,如果当前数值不能再扩张了,就把之前记录下的所有数值共同构成一个无缝子序列,计算其拓展长度并更新最大值。
注意最后输出 \(n\) 减去这个最大值。
最后看在题解写的这么详细的份上,给个关注呗
喜闻乐见的代码:
#include <bits/stdc++.h>
using namespace std;
int n;
int a[1000005];
int b[1000005];
int l[1000005], r[1000005];
int cnt[1000005] = {};
int pos[1000005] = {}, pre[1000005], nxt[1000005] = {};
int cal(int mn, int mx) { //计算最小值为mn最大值为mx 的无缝子序列拓展长度
int res = 0;
for (int i = mn; i <= mx; i++) //先加上子序列内的数个数
res += cnt[i];
for (int i = l[mn - 1]; i && i < l[mn]; i = nxt[i]) //在mn左边的mn-1个数
res++;
for (int i = r[mx + 1]; i > r[mx]; i = pre[i]) //在mx右边的mx+1个数
res++;
return res;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
b[a[i]] = 1;
}
for (int i = 1; i <= 1000000; i++)
b[i] += b[i - 1];
for (int i = 1; i <= n; i++)
a[i] = b[a[i]]; //离散化
for (int i = 1; i <= n; i++)
if (l[a[i]] == 0)
l[a[i]] = i;
for (int i = n; i >= 1; i--)
if (r[a[i]] == 0)
r[a[i]] = i;
for (int i = 1; i <= n; i++) {
pre[i] = pos[a[i]];
pos[a[i]] = i;
}
memset(pos, 0, sizeof pos);
for (int i = n; i >= 1; i--) {
nxt[i] = pos[a[i]];
pos[a[i]] = i;
} //求辅助数组们
int UL = 0;
for (int i = 1; i <= n; i++) {
UL = max(UL, a[i]); //求最大值好遍历
cnt[a[i]]++;
}
int lst = 1;
int ans = 0;
for (int i = 2; i <= UL + 1; i++)
if (r[i - 1] > l[i] || i == UL + 1) {
ans = max(ans, cal(lst, i - 1));
lst = i;
}
printf("%d", n - ans);
return 0;
}