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;
}
posted @ 2024-02-19 11:00  FLY_lai  阅读(8)  评论(0编辑  收藏  举报