Codeforces Round 963 (Div. 2) D题解

Codeforces Round 963 D

题目描述

给定两个正整数 nk 以及另一个由 n 个整数组成的数组 a
在一次操作中,可以选择 a 中大小为 k 的任意一个子数组,然后将其从数组中删除,而不改变其他元素的顺序。更正式地说,假设 (l, r) 是对子数组 a[l], a[l+1], ..., a[r] 的操作,使得 r-l+1=k ,那么执行此操作意味着将 a 替换为 [a[1], ..., a[l-1], a[r+1], ..., a[n]]
例如,如果 a=[1, 2, 3, 4, 5] 在这个数组上执行了 (3, 5) 操作,那么它就会变成 a=[1, 2] 。此外,操作 (2, 4) 的结果是 a=[1, 5] ,操作 (1, 3) 的结果是 a=[4, 5]
a 的长度大于 k (即 |a| > k )时,你必须重复操作。处理后,数组 a 中所有剩余元素的最大中值是多少?
长度为 n 的数组的中位数是我们对元素进行非递减排序后索引为 ⌊ (n+1)/2 ⌋ 的元素。例如 median([2, 1, 5, 4, 3]) = 3median([5]) = 5median([6, 8, 2, 4]) = 4

解题思路

  1. 这题需要想到二分搜索可行解,复杂度在log(Max(a)).
  2. 需要理解,这题找的是最大解,所以对于二分的每一个x,只需要找是否有某个终态满足 其中的>=x的数字多于一半。例如1,2,4,5,5,6。对于4是满足的,对于5不满足。同时对于1也满足,可能会存在疑惑,1不可能是中位数,但是我们找的实际上是最大解,所以1可行的含义应该是这个解可以>=1。如果满足,二分会一直向后推导到4也就是答案。因为比4大的所有x都被认为不满足。
  3. 考虑如何进行判断,这里我们使用dp来访问可达状态,我们的目标应该是保证到达终态时>=x的数尽量的多。这时候其实就是看一个序列大于等于x的数有多少个。
  4. 考虑如何转移状态,这里我们对编号减一并且取模(把index都减1了方便后续判断)。对于每一个i,它可以由哪里转移过来?没有任何数字时>=x的数量为0设dp[0] = 0。第一个状态很显然,它可以由dp[0]转移而来,所以dp[1] = dp[0]+ (a[1] >= x ? 1 : -1)
  5. 为啥这里取-1和1,因为这样最后只要大于0就可以说明>=x数量多余<x,方便判断
  6. 对于不在第一行的序号,它首先可以继续由它左方的状态转移而来,并且它还可以放弃自身的位置,也就是继续让上一行的状态保留。
    dp[i] = max(dp[i-1] + (a[i] >= x ? 1 : -1), dp[i-k])
  7. 但是对于每一行的第一个点,没有左端的状态给他转移,也就是它不能接续前面的序列,因为这个序列长度它不能>k。它现在面临两个选择,第一是新开一个序列,也就是把前一个状态ci更新为0,或者它还可以和其它点一样,放弃自己,继续继承上一行的0位置。表现在操作上就是index 1 2 3 4 选择删掉1 2 3还是删掉2 3 4。
  8. 至此整个题目就解决了。以下面的样例举例,实际上就是告诉你只能选两个位于0和1的数,并且0和1需要满足一个顺序,0选择的数字在a[i]中必须出现在1的前方。朴素做法是枚举所有可能组合来选择,但是最大数量是可以逐步转移过去的,所以只需要在n次操作里面dp。
n = 8, k = 3
index 1 2 3 4 5 6 7 8
mod   0 1 2 0 1 2 0 1

拆成多段 状态只能由左向右转移或者正下方替代正上方的状态
因为每次要删去连续的k个,可以亲手试试来验证。
0->1->2
|  |  |
0->1->2
|  | 
0->1

代码实现

#include <bits/stdc++.h>
#define int long long
using namespace std;
void Solve(){
	int n, k;cin>>n>>k;
	int a[n+1];
	for(int i = 1; i <= n;i ++){
		cin>>a[i];
	}
	int l = 0, r = 1e9 + 10, mid;
	while(l < r){
		mid = (l + r + 1) >> 1;
		vector<int> dp(n+1, -LLONG_MAX);
		dp[0] = 0;
		for(int i = 1; i <= n; i++){
			int ci;
			ci = dp[i-1];
			if((i-1) % k == 0) ci = 0;
			int is = a[i] >= mid ? 1 : -1;
			dp[i] = max(dp[i], ci + is);
			if(i > k) dp[i] = max(dp[i], dp[i - k]);
		}
		if(dp[n] > 0){
			l = mid;
		}else{
			r = mid - 1;
		}
	}
	cout<<l<<endl;
}

int32_t main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int T;cin>>T;while(T--)
		Solve();
	return 0;
}

复杂度分析

  • 时间复杂度: nlog(max(a))
posted @   Hamster_Luter  阅读(63)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示