2025 CSP-S 模拟赛 Day 19 复盘

T1

题意

你有一个数组 a1,a2,…,ana_1,a_2,…,a_na1,a2,,an,每次你可以进行如下操作:

  • 在数组中选择一个数,把它从数组中删除,然后加入数组的开头或者末尾。

问最少多少次能将数组变为单调不减的。

解法

相当于问有多少个数必须进行操作,有多少个数可以不动,设可以不动的数字的下标为 b1,b2,...,bmb_1,b_2,...,b_mb1,b2,...,bm,则 bbb 需要满足如下条件:

  1. 1≤b1<b2<...<bm≤n1 \le b_1 < b_2 < ... < b_m \le n1b1<b2<...<bmn
  2. ab1≤ab2≤...≤abma_{b_1} \le a_{b_2} \le ... \le a_{b_m}ab1ab2...abm
  3. ∀i∈[1,n]\forall i \in [1,n]i[1,n]∃j∈[1,m]\exists j \in [1,m]j[1,m] 满足 i=bji = b_ji=bjai≤ab1a_i \le a_{b_1}aiab1ai≥abma_i \ge a_{b_m}aiabm

证明简单说明③:
因为除了 bbb 中的数都进行了操作,所以所有数必然在 bbb 中的数的前或后,若不满足条件,则数组无序,然而想让数组重新有序,需要把 bbb 中的数移到开头或结尾,不满足定义。

现在问题转化为求 mmm 的最大值,容易发现,bbb 中的数可以分为 333 个部分(不一定 333 个部分都有):

  1. xxx 出现位置下标序列的前缀
  2. [x+1,y−1][x + 1,y - 1][x+1,y1] 出现位置的下标序列
  3. yyy 出现位置下标序列的后缀

②③的长度可以用类似 dp 的方法预处理,然后枚举①,细节非常多,总复杂度 O(n)O(n)O(n)

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 1931931931;
const int N = 3e5 + 31;
const int M = 1e6 + 31;

int n,a[N],b[N],c[M],nxt[N],f[M],g[N],t[M],v[M];

void solve(){
	scanf("%d",&n); c[0] = n + 1; t[0] = 0; v[0] = n + 1;
	for(int i = 1;i <= n;i++)
		scanf("%d",&a[i]), b[i] = a[i], c[a[i]] = n + 1, g[i] = 0, t[a[i]] = 0;
	for(int i = n;i >= 1;i--)
		v[a[i]] = i;
	g[n + 1] = 0;
	sort(b + 1,b + n + 1);
	int m = unique(b + 1,b + n + 1) - b - 1;
	for(int i = 1;i < m;i++)
		f[b[i]] = b[i + 1];
	f[b[m]] = 0;
	int res = 0;
	for(int i = n;i >= 1;i--){
		nxt[i] = c[a[i]];
		if(c[a[i]] == n + 1)
			nxt[i] = (a[i] == b[m] ? n + 1 : v[f[a[i]]]);
		if(nxt[i] == n + 1 || nxt[i] < i)
			g[i] = t[f[a[i]]] + 1;
		else
			g[i] = g[nxt[i]] + 1;
		res = max(res,g[i]);
		c[a[i]] = i; t[a[i]]++;
	}
	for(int i = 1;i < m;i++){
		int j = v[b[i]], tmp = 0;
		while(j < v[b[i + 1]])
			j = nxt[j], tmp++;
		res = max(res,tmp + g[v[b[i + 1]]]);
	}
	for(int i = 1;i <= n;i++)
	 	v[a[i]] = 0;
	for(int i = 1;i <= n;i++){
		v[a[i]]++; t[a[i]]--;
		res = max(res,v[a[i]] + t[f[a[i]]]);
	}
	printf("%d\n",n - res);
}

int main(){
    int t; scanf("%d",&t);
    while(t--) solve();
}

T2

题意

给定一个数组 a1,a2,…,ana_1,a_2,…,a_na1,a2,,an,有 qqq 组询问,每组询问给定 kkk,求:

maxr−l+1=kmex{al,al+1,...ar} max_{r - l + 1 = k} mex \lbrace a_l,a_{l + 1},...a_r \rbracemaxrl+1=kmex{al,al+1,...ar}

解法

有一个结论是,对于所有的询问,一定可以被拆成 O(n)O(n)O(n) 个四元组 (l0,l1,r0,r1)(l_0,l_1,r_0,r_1)(l0,l1,r0,r1),保证对所有 l0≤l≤l1,r0≤r≤r1l_0 \le l \le l_1,r_0 \le r \le r_1l0ll1,r0rr1 这样的询问,[l,r][l,r][l,r] 的 mex 都是相同的。
对于一类长度越长,值单调递增或者递减的区间,如果要求最值,那么越短的区间肯定比较长的区间要优,所以我们要把较长的区间直接删掉,最后就剩下了 O(n)O(n)O(n) 个不交区间。我们可以直接求这个不交区间。这个过程可以用一些手法实现。

代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 31;
const int inf = 1931931931;

int n,q,a[N],f[N],p[N],v[N],nxt[N],ans[N],g[N];

int main(){
	scanf("%d",&n);
	for(int i = 1;i <= n;i++)
		scanf("%d ",&a[i]);
	int k = 0;
	memset(f,127,sizeof(f));
	for(int i = 1;i <= n;i++){
		v[a[i]] = 1;
		while(v[k]) 
			f[k] = i, k++;
		nxt[p[a[i]]] = i;
		p[a[i]] = i;
	}
	for(int i = k;i >= 0;i--)
		ans[i] = f[i];
	f[k + 1] = inf; k++;
	for(int i = 1;i <= n;i++){
		int tp = a[i];
		if(!nxt[i]){
			while(tp < k)
				ans[tp] = min(ans[tp],f[tp] - i + 1), f[tp++] = inf;
			k = a[i];
		}
		while(tp < k && f[tp] < nxt[i]){
			ans[tp] = min(ans[tp],f[tp] - i + 1);
			f[tp] = nxt[i]; tp++;
		}
	}
	scanf("%d",&q);
	for(int i = 1;i <= n;i++){
		g[i] = g[i - 1];
		while(i == ans[g[i]])
			g[i]++;
	}
	while(q--){
		int x; scanf("%d",&x);
		printf("%d\n",g[x]);
	}
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值