2024/9/15 CSP-S daimayuan模拟赛复盘

2024/9/15 CSP-S daimayuan

contest link (Day 3)

A. 达到巅峰

题面描述

Tourist 共参加了 \(n\) 场 codeforces 举办的积分比赛,参加完第 \(i\) 场比赛后,Tourist 的积分被更新为 \(a_i\)。我们称第 \(i\) 场比赛结束后,Tourist 的积分「达到巅峰」当且仅当对于所有小于 \(i\) 的正整数 \(j\),都有 \(a_j<a_i\)

Tourist 认为他的比赛经历中,越多次比赛结束后他的积分「达到巅峰」,越显得他一直在进步。所以他想把至多一次比赛的积分记录删除,使得他「达到巅峰」的次数尽可能多。

例如,若 Tourist 共参加过 \(5\) 次比赛,\(5\) 次比赛结束后积分依次为 \(1500,1900,1700,1850,2000\),他在第 \(1,2,5\) 三场比赛结束后是「达到巅峰」的,但若把第 \(2\) 场比赛的积分记录删除,剩下的四场比赛积分记录为:\(1500,1700,1850,2000\),那么每场比赛结束后都「达到巅峰」,故删除第 \(2\) 次记录能使得 Tourist 「达到巅峰」的次数变为最大值。删除任何其他记录都无法使「达到巅峰」次数多达 \(4\) 次。

现在请你帮 Tourist 计算,在至多删除一次比赛的积分记录的情况下,他达到巅峰的次数最多能有几次?

输入 & 输出 & 样例 & 数据范围

输入第一行一个正整数 \(n\),代表 Tourist 共参加了几场积分比赛。

接下来一行,有 \(n\) 個正整数 \(a_1,a_2,\dots,a_n\)

输出一个整数表示答案。

对于所有的数据,保证 \(1 \le n \le 5 \times 10^5,1 \le a_i < 10^6\)

5
1500 1900 1700 1850 2000

4
5
5 2 5 3 5

2

思路解析

可以发现最后删除的那一次的记录肯定在删除前就是一个巅峰,于是我们只用考虑每一个巅峰对答案造成的贡献即可。

这时我们发现对于删除每一个原巅峰后,有可能新成为巅峰的点只可能出现在“当前巅峰和下一个巅峰之间”,同时可能成为新的巅峰的点还得比区间内所有之前的点都大,这样我们就能存下来每两个巅峰之间所有比该区间里之前的值都大的点。

但是如果一个区间内的点想要成为新的巅峰还有一个要求,就是需要比“当前被删除的巅峰的前一个巅峰”还要高,否则依然无法成为巅峰。但是由于我们存下来区间内的点都是有序的,所以可以直接通过二分找到有几个点超过前一个巅峰。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int n, a[N], top[N], pos[N];
vector<int> v[N];
int main() {
	cin >> n;
	int mx = 0, s = 0;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
		if(a[i] > mx) top[i] = ++s, pos[s] = i;
		mx = max(mx, a[i]);
	}
	mx = 0; int cnt = 0;
	for(int i = 1; i <= n; i++) {
		if(top[i]) mx = 0, cnt++;
		else if(a[i] > mx) v[cnt].push_back(a[i]), mx = a[i];
	}
	int ans = s;
	for(int i = 1; i <= s; i++) {
		int t = (v[i].end() - upper_bound(v[i].begin(), v[i].end(), a[pos[i - 1]]));
		ans = max(ans, s - 1 + t);
	}
	cout << ans;
	return 0;
}

B. 子集数量

题面描述

给你包含 \(N\) 个整数 \(a_1,a_2,\dots,a_N\) 的多重集 (multiset) \(S\),此多重集有 \(2^N\) 个子集。

定义一个子集的价值为此子集所有数字的和。

给定 \(3\) 个正整数 \(K,L,R\),请输出 \(S\) 中元素数量大于等于 \(L\) 且小于等于 \(R\) 的所有子集中,价值前 \(K\) 大的子集的价值和。

输入 & 输出 & 样例 & 数据范围

输入的第一行包含 \(4\) 个正整数 \(N,K,L,R\)

输入的第二行包含 \(N\) 个整数 \(a_1,a_2,\dots,a_N\)

输出一个整数代表答案。

对于所有数据,保证 \(2 \le N \le 2 \times 10^5,1 \le \min(2×10^5,\sum^{R}_{i=L}\binom{N}{i}),1 \le L \le R \le N ,−10^6 \le ai \le 10^6\)

3 2 2 2
2 4 1

11
5 4 3 4
-3 -1 -1 0 3

6
10 234 3 7
524399 920312 697796 866354 762423 574254 790514 483143 926875 290091

1070123901

思路解析

可以发现 \(K\) 不大,也就是说复杂度大概率会跟 \(K\) 有关。考虑到统计的是元素数量在 \([L,R]\) 区间中的所有子集,所以我们可以理解为把所有元素数量相同的子集放到同一个大的集合中,这样就是在这总共 \(R-L+1\) 个集合中找到前 \(K\) 个大的元素总和。将问题转化到这个程度后就可以在 \(O(K)\) 的复杂度的情况下解决这个子问题,具体的解决方法可以用一个大根堆实现,将每一个大集合中的最大元素压入堆顶,然后执行 \(K\) 次,每次弹出堆顶,然后尝试将堆顶的这种最优的情况经过一或二次变换转换成一个次优的情况(具体就是我们的堆里面存的内容是一个集合的总和值,于是我们考虑移动该集合的一个位置,例如集合 \(111000\) 变换成 \(110100\),此时我们也不需要存下来整个集合,只需要存下来当前变换的位置和左边的两个变换的位置即可)。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
int n, k, l, r, a[N], s[N];
bool cmp(int x, int y) {return x > y;}
struct node {
	int w, x, y, z;
	bool operator < (const node &rhs) const {
		return rhs.w > w;
	}
};
signed main() {
	cin >> n >> k >> l >> r;
	for(int i = 1; i <= n; i++) cin >> a[i];
	sort(a + 1, a + n + 1, cmp);
	for(int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
	priority_queue<node> q;
	for(int i = l; i <= r; i++) {
		q.push({s[i], i + 1, 0, 0});
	}
	int ans = 0;
	while(k--) {
		node u = q.top(); q.pop();
		ans += u.w;
		if(u.y <= n && u.y > u.z + 1) q.push({u.w + a[u.y] - a[u.y - 1], u.x, u.y - 1, u.z});
		if(u.x <= n && u.x > u.y + 1) q.push({u.w + a[u.x] - a[u.x - 1], u.x + 1, u.x - 1, u.y});
	}
	cout << ans;
	return 0;
}
posted @ 2024-09-16 00:11  2020luke  阅读(122)  评论(0)    收藏  举报