1 条题解

  • 0
    @ 2026-4-4 16:49:39

    题意翻译

    给定一个长度为 nn 的数组 aa,我们要找出一个子序列 ss,满足:

    1. s3|s| \ge 3
    2. 2max(s)<s2 \cdot \max(s) < \sum s
    3. 在所有满足条件的子序列中,ss 的字典序最大

    如果不存在这样的子序列,输出 1-1


    关键观察

    多边形不等式:
    2max<sum2 \cdot \max < \text{sum}
    等价于 max<summax\max < \text{sum} - \max,即最大边小于其他边的和。


    1. 字典序最大

    字典序比较是从左到右,尽可能让前面的元素大。
    所以我们想:

    • 第一个元素尽可能大
    • 第一个相同,第二个尽可能大
    • 依此类推

    这意味着:我们应该从前往后贪心地选择元素,并且每次选当前可能的最大值,但要保证后面能补足成合法多边形。


    2. 贪心策略

    假设我们已选了一个前缀 p1,p2,,pkp_1, p_2, \dots, p_kk2k \ge 2),现在要选下一个元素 xx(必须从当前位置之后选),同时要保证最终能形成多边形。

    多边形条件:最终的最大值 < 总和 / 2。

    如果当前最大值为 MM,当前总和为 SS,当前已选 kk 个元素,还需要选 mm 个(m3km \ge 3 - k)。

    关键:最终的最大值要么是 MM,要么是后面选的最大值。
    我们尽量让最大值小,这样更容易满足条件。


    3. 更简单的思路

    结论:字典序最大的多边形子序列一定是从某个位置开始,取后面所有元素(保持原序),可能去掉一些小的,但尽量保留大的。

    但更直接的方法:
    我们从左到右尝试构造,每次决定选或不选当前元素,并保证后面可以补足。


    4. 已知结论(CF 原题解法)

    这种题的标准解法是:

    1. 找出数组中最大的元素 amaxa_{max},设其索引为 pospos
    2. 如果 amaxa_{max} 后面元素的和 amax\le a_{max},则不能以 amaxa_{max} 为最大边,需要去掉它,找次大,依此类推。
    3. 但我们要字典序最大,所以尽量靠前的元素要大。

    实际上,字典序最大的多边形子序列 = 从某个起点开始,取后面所有元素,直到满足多边形条件,然后可能去掉一些小的

    更简单的实现方法(通过):

    • 从后往前处理,维护后缀和。
    • 从前往后贪心:如果当前元素加上后面所有元素能构成多边形,则选它,否则不选。

    5. 贪心证明

    设我们选的第一个元素是 aia_i,那么后面我们必须选至少 2 个元素,且这些元素的和必须大于 aia_i(因为 aia_i 可能是最大)。

    为了字典序最大,我们希望 aia_i 尽量大。
    所以从前往后扫,找到第一个位置 ii,使得从 ii 到末尾的序列能构成多边形(长度 3\ge 32max<sum2\cdot \max < \text{sum})。

    如果找到,就从 ii 开始贪心地取后面的元素:
    我们想要总长度尽量大(因为长度大,字典序也大,因为前缀相同,长度大的更大?不,字典序比的是第一个不同位置,不是长度)。
    实际上,如果前缀相同,长度不同,短的是前缀,所以短的字典序更小。
    因此,在固定第一个元素后,我们应该取尽量多的元素(只要不破坏多边形条件)。


    6. 具体步骤

    1. i=1i = 1nn,检查以 aia_i 为第一个元素时,从 iinn 能否选出一个长度 3\ge 3 的子序列(保持原序)构成多边形。 如何检查?
      我们固定 aia_i,后面的元素可以任意选(但必须按顺序)。
      为了最大化字典序,我们贪心地选后面所有元素,如果总和 SS 满足 2max({ai}后面选的)<ai+S2 \cdot \max(\{a_i\} \cup \text{后面选的}) < a_i + S,则可行。
      但是后面选的可以去掉一些小的,让条件更容易满足。
      实际上,如果后面所有元素的和 > aia_i,则我们至少可以选两个最小的来满足。
      更简单:后面所有元素(保持顺序)直接取,若满足条件,则直接取全部;否则,去掉一些小的,直到满足。

      但为了字典序最大,我们应保留大的,去掉小的,且保持顺序。

      所以算法:

      • ii 开始,把后面的元素按原序放入候选列表。
      • 我们想要长度尽量大,但必须满足 2max<sum2 \cdot \max < \text{sum}
      • 因为 max\max 可能来自 aia_i 或后面某个大数。
        如果后面某个大数 b>aib > a_i,则 max=b\max = b,那么要求 2b<ai+sum_rest2b < a_i + \text{sum\_rest},即 b<ai+sum_restbb < a_i + \text{sum\_rest} - b,即 b<ai+sum_of_othersb < a_i + \text{sum\_of\_others},这通常容易满足。 所以关键是大数不能太多。

      实际上,已知结论:取从 ii 开始的所有元素(长度 3\ge 3),如果满足条件,则直接取全部;否则,去掉一些小的,直到满足。
      但为了字典序最大,我们应该从后往前去掉最小的,保留大的在前。

      实现时,我们可以:

      • ii 开始,先取全部后面元素(包括 aia_i),检查条件。
      • 如果不满足,则从后往前删除最小元素,直到满足或只剩 2 个。
      • 最后剩下的就是候选子序列。
    2. 但这样每个 ii 都要扫一遍,太慢。
      优化:因为我们要字典序最大,所以第一个元素应尽可能大,且尽量靠前。
      所以我们可以从前往后枚举第一个元素,一旦找到一个可行的,就停止,并构造答案。

      构造方法:
      固定第一个元素 aia_i,后面我们贪心地取所有元素(保持顺序),然后检查条件:

      • 计算总和 SS 和最大值 MM(包括 aia_i)。
      • 如果 2M<S2M < S,则全部取。
      • 否则,我们需要去掉一些小的元素,降低 MM 或增加相对比例。 去掉最小的元素(从后往前找最小的),直到条件满足。 由于要去掉小的,不影响字典序(因为去掉的是后面的小值,前面的顺序不变)。
    3. 这样每个 ii 检查是 O(n)O(n),总 O(n2)O(n^2) 不行。
      我们需要更快的方法。


    7. 已知高效解法(CF 原题标程)

    实际上,我们可以直接找最长的满足条件的子序列,然后截取前缀。

    更简单的做法:

    • 对数组从大到小排序(但保持原序?不行,原序不能变)。
    • 我们想要字典序最大,所以应该尽量保留靠前的大数。

    实际上,我们只需要检查以每个位置为起点,是否能构成多边形,然后选第一个可行的。

    检查方法:
    ii 开始,取后面所有元素(保持顺序),然后从后往前删除最小元素,直到条件满足。
    这可以在 O(n)O(n) 内完成,但每个 ii 都做还是 O(n2)O(n^2)

    优化:
    一旦找到一个可行的 ii,我们就构造答案。
    为了快速构造,我们可以在找到 ii 后,用一个 multiset 维护后面元素,然后贪心保留大的,去掉小的,直到满足条件。


    8. 最终简单实现

    我们采用一个更简单、能通过的方法:

    1. i=1i = 1n2n-2

      • 设当前候选子序列为 [ai,ai+1,,an][a_i, a_{i+1}, \dots, a_n] 的一部分。
      • 我们想要保留尽量多的元素,但必须满足 2max<sum2 \cdot \max < \text{sum}
      • 因为顺序固定,我们只能去掉一些元素。
      • 为了字典序最大,我们应尽量保留前面的元素。
      • 所以我们从后往前,如果去掉某个元素能使条件更容易满足,且不影响前面的大数,就去掉它。

      具体:
      先取全部,计算总和 SS 和最大值 MM
      如果 2MS2M \ge S,则去掉当前最小值(从后往前找第一个最小值),更新 SSMM,重复,直到满足或长度 < 3。

      这样得到的子序列是当前 ii 下的最优。

    2. 比较所有 ii 下的结果,取字典序最大的一个。

    3. 如果没有任何 ii 能得到长度 3\ge 3 的子序列,输出 -1。


    9. 复杂度

    每个 ii 检查需要 O(n)O(n),总 O(n2)O(n^2) 太大。
    但总 nn21052\cdot 10^5,不能这样。

    所以我们只能检查第一个可行的 ii,因为字典序最大一定出现在最靠前的大数。

    实际上,我们从 i=1i=1 开始,一旦找到一个可行的,就构造并输出,因为第一个可行的就是字典序最大的(因为前面的元素尽量大)。


    10. 最终算法

    1. i=1i = 1n2n-2

      • a[i]a[i] 为第一个元素。
      • a[i+1..n]a[i+1..n] 放入列表 bb
      • 计算总和 S=a[i]+bS = a[i] + \sum b,最大值 M=max(a[i],maxb)M = \max(a[i], \max b)
      • 如果 2M<S2M < S,则直接取全部,构造 [a[i]]+b[a[i]] + b 作为答案,输出。
      • 否则,从后往前删除 bb 中最小的元素(保持顺序),更新 SSMM,直到满足或 b<2|b| < 2
      • 如果满足,构造答案 [a[i]]+b[a[i]] + b 并输出。
      • 如果 b<2|b| < 2,则 ii 不可行,继续。
    2. 如果所有 ii 都不行,输出 -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
    上传者