P3411 序列变换 题解

自己做不出来,看现在题解区的题解讲的都不咋清楚。懂了之后来为后人铺路。而且我的马蜂比较好看

题目传送门

我能看懂这道题,主要是依靠了这篇题解的帮助。

首先我们只关注数的相对关系,所以可以离散化。注意到值域 106,用数组离散化。

这道题可以用贪心做。(有一些定义先往下看)

定义一个无缝子序列:一个子序列中最小值为 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 左边有 cnt1mn1。最后一个 mx 右边有 cnt2mx+1。称这个无缝子序列的拓展长度为 len+cnt1+cnt2

现在抛出贪心的结论:答案就是 nxx 是最大的 “无缝子序列拓展长度”。注意这里表示的不是 “最长的无缝子序列” 的拓展长度。

为什么呢?首先我们证明存在一组解,可以构造出 nx 次操作。

假设无缝子序列 {num} 使得它的拓展长度为 x

那就可以固定 {num} 中所有数和所有计算拓展长度时统计到的 mn1mx+1。这些数不进行操作。

剩下所有数,分成两拨:小于 mn 的和大于 mx 的。

将小于 mn 的数从大到小排序,依次将每个数放在开头;将大于 mx 的数从小到大排序,依次将每个数放在结尾。

这样每个没固定的数都使用了一次操作,共 nx 次操作使得整个序列排好了序。

下面证明不存在比 nx 还小的解。假设更小的解是 k

那么说明至少有 nk 个数没有进行操作。这些没进行操作的数一定形成一个 “无缝子序列拓展” 的形式。因为假如它中间有数没有出现,一定需要将外界的数移到中间来,这需要移动这 nk 个数;假如它不是单调不减,需要重新排序,也需要移动这些数。

综上所述,最终的答案就是 nx

那具体怎么求呢?对每个位置求出 nxtpre 表示后一个/前一个和它相等的数的所在位置。再对每一种数值求出 l,r 表示这种数值最左边的数和最右边的数的位置。还有每种数值的出现次数 cnt

一个无缝子序列的充要条件是:对于任意 x<yx,y 属于无缝子序列,rx<ly

发现如果 rx<ly,ry<lzrx<lz
那么可以从小到大遍历每种数值,如果当前数值不能再扩张了,就把之前记录下的所有数值共同构成一个无缝子序列,计算其拓展长度并更新最大值。

注意最后输出 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;
}
posted @   FLY_lai  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示