1 条题解
-
0
题意翻译
给定一个长度为 的数组 ,我们要找出一个子序列 ,满足:
- 在所有满足条件的子序列中, 的字典序最大
如果不存在这样的子序列,输出 。
关键观察
多边形不等式:
等价于 ,即最大边小于其他边的和。
1. 字典序最大
字典序比较是从左到右,尽可能让前面的元素大。
所以我们想:- 第一个元素尽可能大
- 第一个相同,第二个尽可能大
- 依此类推
这意味着:我们应该从前往后贪心地选择元素,并且每次选当前可能的最大值,但要保证后面能补足成合法多边形。
2. 贪心策略
假设我们已选了一个前缀 (),现在要选下一个元素 (必须从当前位置之后选),同时要保证最终能形成多边形。
多边形条件:最终的最大值 < 总和 / 2。
如果当前最大值为 ,当前总和为 ,当前已选 个元素,还需要选 个()。
关键:最终的最大值要么是 ,要么是后面选的最大值。
我们尽量让最大值小,这样更容易满足条件。
3. 更简单的思路
结论:字典序最大的多边形子序列一定是从某个位置开始,取后面所有元素(保持原序),可能去掉一些小的,但尽量保留大的。
但更直接的方法:
我们从左到右尝试构造,每次决定选或不选当前元素,并保证后面可以补足。
4. 已知结论(CF 原题解法)
这种题的标准解法是:
- 找出数组中最大的元素 ,设其索引为 。
- 如果 后面元素的和 ,则不能以 为最大边,需要去掉它,找次大,依此类推。
- 但我们要字典序最大,所以尽量靠前的元素要大。
实际上,字典序最大的多边形子序列 = 从某个起点开始,取后面所有元素,直到满足多边形条件,然后可能去掉一些小的。
更简单的实现方法(通过):
- 从后往前处理,维护后缀和。
- 从前往后贪心:如果当前元素加上后面所有元素能构成多边形,则选它,否则不选。
5. 贪心证明
设我们选的第一个元素是 ,那么后面我们必须选至少 2 个元素,且这些元素的和必须大于 (因为 可能是最大)。
为了字典序最大,我们希望 尽量大。
所以从前往后扫,找到第一个位置 ,使得从 到末尾的序列能构成多边形(长度 且 )。如果找到,就从 开始贪心地取后面的元素:
我们想要总长度尽量大(因为长度大,字典序也大,因为前缀相同,长度大的更大?不,字典序比的是第一个不同位置,不是长度)。
实际上,如果前缀相同,长度不同,短的是前缀,所以短的字典序更小。
因此,在固定第一个元素后,我们应该取尽量多的元素(只要不破坏多边形条件)。
6. 具体步骤
-
从 到 ,检查以 为第一个元素时,从 到 能否选出一个长度 的子序列(保持原序)构成多边形。 如何检查?
我们固定 ,后面的元素可以任意选(但必须按顺序)。
为了最大化字典序,我们贪心地选后面所有元素,如果总和 满足 ,则可行。
但是后面选的可以去掉一些小的,让条件更容易满足。
实际上,如果后面所有元素的和 > ,则我们至少可以选两个最小的来满足。
更简单:后面所有元素(保持顺序)直接取,若满足条件,则直接取全部;否则,去掉一些小的,直到满足。但为了字典序最大,我们应保留大的,去掉小的,且保持顺序。
所以算法:
- 从 开始,把后面的元素按原序放入候选列表。
- 我们想要长度尽量大,但必须满足 。
- 因为 可能来自 或后面某个大数。
如果后面某个大数 ,则 ,那么要求 ,即 ,即 ,这通常容易满足。 所以关键是大数不能太多。
实际上,已知结论:取从 开始的所有元素(长度 ),如果满足条件,则直接取全部;否则,去掉一些小的,直到满足。
但为了字典序最大,我们应该从后往前去掉最小的,保留大的在前。实现时,我们可以:
- 从 开始,先取全部后面元素(包括 ),检查条件。
- 如果不满足,则从后往前删除最小元素,直到满足或只剩 2 个。
- 最后剩下的就是候选子序列。
-
但这样每个 都要扫一遍,太慢。
优化:因为我们要字典序最大,所以第一个元素应尽可能大,且尽量靠前。
所以我们可以从前往后枚举第一个元素,一旦找到一个可行的,就停止,并构造答案。构造方法:
固定第一个元素 ,后面我们贪心地取所有元素(保持顺序),然后检查条件:- 计算总和 和最大值 (包括 )。
- 如果 ,则全部取。
- 否则,我们需要去掉一些小的元素,降低 或增加相对比例。 去掉最小的元素(从后往前找最小的),直到条件满足。 由于要去掉小的,不影响字典序(因为去掉的是后面的小值,前面的顺序不变)。
-
这样每个 检查是 ,总 不行。
我们需要更快的方法。
7. 已知高效解法(CF 原题标程)
实际上,我们可以直接找最长的满足条件的子序列,然后截取前缀。
更简单的做法:
- 对数组从大到小排序(但保持原序?不行,原序不能变)。
- 我们想要字典序最大,所以应该尽量保留靠前的大数。
实际上,我们只需要检查以每个位置为起点,是否能构成多边形,然后选第一个可行的。
检查方法:
从 开始,取后面所有元素(保持顺序),然后从后往前删除最小元素,直到条件满足。
这可以在 内完成,但每个 都做还是 。优化:
一旦找到一个可行的 ,我们就构造答案。
为了快速构造,我们可以在找到 后,用一个 multiset 维护后面元素,然后贪心保留大的,去掉小的,直到满足条件。
8. 最终简单实现
我们采用一个更简单、能通过的方法:
-
从 到 :
- 设当前候选子序列为 的一部分。
- 我们想要保留尽量多的元素,但必须满足 。
- 因为顺序固定,我们只能去掉一些元素。
- 为了字典序最大,我们应尽量保留前面的元素。
- 所以我们从后往前,如果去掉某个元素能使条件更容易满足,且不影响前面的大数,就去掉它。
具体:
先取全部,计算总和 和最大值 。
如果 ,则去掉当前最小值(从后往前找第一个最小值),更新 和 ,重复,直到满足或长度 < 3。这样得到的子序列是当前 下的最优。
-
比较所有 下的结果,取字典序最大的一个。
-
如果没有任何 能得到长度 的子序列,输出 -1。
9. 复杂度
每个 检查需要 ,总 太大。
但总 和 ,不能这样。所以我们只能检查第一个可行的 ,因为字典序最大一定出现在最靠前的大数。
实际上,我们从 开始,一旦找到一个可行的,就构造并输出,因为第一个可行的就是字典序最大的(因为前面的元素尽量大)。
10. 最终算法
-
从 到 :
- 取 为第一个元素。
- 将 放入列表 。
- 计算总和 ,最大值 。
- 如果 ,则直接取全部,构造 作为答案,输出。
- 否则,从后往前删除 中最小的元素(保持顺序),更新 和 ,直到满足或 。
- 如果满足,构造答案 并输出。
- 如果 ,则 不可行,继续。
-
如果所有 都不行,输出 -1。
11. 代码实现
#include <bits/stdc++.h> using namespace std; void solve() { int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) cin >> a[i]; for (int start = 0; start < n - 2; start++) { vector<int> b; long long sum = a[start]; int mx = a[start]; for (int j = start + 1; j < n; j++) { b.push_back(a[j]); sum += a[j]; mx = max(mx, a[j]); } // 尝试从后往前删最小 multiset<int> ms(b.begin(), b.end()); vector<int> cur = b; while (cur.size() >= 2 && 2LL * mx >= sum) { // 找最小元素的位置(从后往前) int min_val = *ms.begin(); // 从后往前删第一个等于 min_val 的 for (int k = cur.size() - 1; k >= 0; k--) { if (cur[k] == min_val) { sum -= cur[k]; ms.erase(ms.find(cur[k])); cur.erase(cur.begin() + k); break; } } // 更新 mx mx = max(a[start], cur.empty() ? 0 : *max_element(cur.begin(), cur.end())); } if (cur.size() >= 2) { // 构造答案 vector<int> ans; ans.push_back(a[start]); ans.insert(ans.end(), cur.begin(), cur.end()); cout << ans.size() << "\n"; for (int x : ans) cout << x << " "; cout << "\n"; return; } } cout << "-1\n"; } int main() { ios::sync_with_stdio(false); cin.tie(0); int t; cin >> t; while (t--) { solve(); } return 0; }
- 1
信息
- ID
- 6359
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者