2024/9/15 CSP-S daimayuan模擬賽覆盤

2020luke發表於2024-09-16

2024/9/15 CSP-S daimayuan

contest link

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;
}