题解:AtCoder AT_awc0129_e Pile of Cards

【题目来源】

AtCoder:E - Pile of Cards

【题目描述】

Takahashi is cleaning up a card game. There are \(N\) cards lined up in a row from left to right on a table. The \(i\)-th card from the left is called "card \(i\)". Card \(i\) has an integer \(A_i\) written on it.

Takahashi will organize these cards by dividing them into several piles. The piles must be created according to the following rules:

  • Process card \(1\), card \(2\), \(\ldots\), card \(N\) in this order, one by one (the order cannot be changed).
  • For each card, choose one of the following operations:
  • Create a new pile and place the card on it.
  • Place the card on top of an already existing pile. However, if the number written on the current top card of that pile is \(x\) and the number on the card to be placed is \(y\), then \(x \geq y\) must hold.

Under these rules, the numbers written on the cards in each pile will be monotonically non-increasing from bottom to top.

Takahashi wants to minimize the number of piles.

Furthermore, for each card, Takahashi wants to record "which card is directly below it". The card directly below card \(i\) refers to the card that was at the top of the pile at the moment card \(i\) was placed on top of that existing pile. If card \(i\) was placed to start a new pile (i.e., it is at the bottom of the pile), there is no card directly below it.

For each card \(i\) (\(i = 1, 2, \ldots, N\)), we define the value \(B_i\) as follows:

  • If there is no card directly below card \(i\) (i.e., it is at the bottom of a pile), \(B_i = 0\).
  • If the card directly below card \(i\) is card \(j\), \(B_i = j\).

Among all ways of piling the cards that minimize the number of piles, find one way that maximizes \(B_1 + B_2 + \cdots + B_N\).

Let \(K\) be the minimum number of piles. Output \(K\) and \(B_1, B_2, \ldots, B_N\).

高橋正在整理一个卡牌游戏。桌子上有 \(N\) 张卡片从左到右排成一排。从左数第 \(i\) 张卡片称为"卡片 \(i\)"。卡片 \(i\) 上写有一个整数 \(A_i\)

高橋将通过将卡片分成若干堆来整理这些卡片。分堆必须按照以下规则进行:

  • 按顺序依次处理卡片 \(1\)、卡片 \(2\)\(\ldots\)、卡片 \(N\)(顺序不能改变)。
  • 对于每张卡片,选择以下操作之一:
    • 创建一个新堆,并将该卡片放在上面。
    • 将该卡片放在已有的堆的顶部。但是,如果该堆当前顶部卡片上的数字为 \(x\),要放置的卡片上的数字为 \(y\),则必须满足 \(x \geq y\)

在这些规则下,每堆中卡片上的数字从底到顶将单调不增。

高橋希望最小化堆的数量。

此外,对于每张卡片,高橋想记录"它正下方是哪张卡片"。卡片 \(i\)正下方卡片指的是在卡片 \(i\) 被放到已有堆顶部时,该堆顶部的那张卡片。如果卡片 \(i\) 被用来创建新堆(即它在堆底),则没有正下方卡片。

对于每张卡片 \(i\)\(i = 1, 2, \ldots, N\)),定义值 \(B_i\) 如下:

  • 如果卡片 \(i\) 没有正下方卡片(即它在堆底),\(B_i = 0\)
  • 如果卡片 \(i\) 的正下方卡片是卡片 \(j\)\(B_i = j\)

在所有使堆数最小的分堆方式中,找出一种使 \(B_1 + B_2 + \cdots + B_N\) 最大化 的方式。

\(K\) 为最小堆数。输出 \(K\)\(B_1, B_2, \ldots, B_N\)

【输入】

\(N\)
\(A_1\) \(A_2\) \(\cdots\) \(A_N\)

  • The first line contains an integer \(N\), representing the number of cards.
  • The second line contains \(N\) space-separated integers \(A_1, A_2, \ldots, A_N\), representing the numbers written on the cards.

【输出】

\(K\)
\(B_1\) \(B_2\) \(\cdots\) \(B_N\)

  • In the first line, output the minimum number of piles \(K\).
  • In the second line, output \(B_1, B_2, \ldots, B_N\) separated by spaces, representing a way of piling that maximizes \(B_1 + B_2 + \cdots + B_N\) among all ways that minimize the number of piles. If there are multiple such ways, you may output any of them.

【输入样例】

5
3 1 4 2 2

【输出样例】

2
0 1 0 3 4

【核心思想】

  1. 问题分析:给定 \(N\) 张按顺序处理的卡片,每张有数字 \(A_i\)。每张卡片要么新建一堆,要么放到已有堆顶(要求堆顶数字 \(x \geq\) 当前卡片数字 \(y\))。求最小堆数 \(K\),并在所有最小堆数的方案中最大化 \(\sum B_i\)\(B_i\) 为卡片 \(i\) 正下方卡片的编号,堆底为 \(0\))。这是一个贪心匹配 + 逆序处理问题,关键在于逆序遍历卡片,用 multiset 维护可匹配的堆顶卡片,将问题转化为最大化匹配对数。

  2. 算法选择

    • 逆序贪心:从右向左处理卡片,将"当前卡片作为下方卡片,右侧某卡片放在其上"的决策转化为匹配问题
    • multiset 维护候选:用有序集合 \(S\) 存储已处理且尚未确定正下方的卡片(即当前可作为堆顶的卡片),按 \((A_i, i)\) 排序
    • 最大匹配策略:对当前卡片 \(u\),在 \(S\) 中找到数字最大且 \(\leq A_u\) 的卡片 \(v\),将 \(v\) 放在 \(u\) 上方(\(u\) 成为 \(v\) 的正下方),最大化 \(B_v = u\)
  3. 关键步骤

    • 逆序遍历\(u\)\(N\)\(1\)):
      • \(S\) 中查找 upper_bound({A_u, n+1}),然后前移一位,找到数字 \(\leq A_u\) 的最大值卡片 \(v\)
      • 若找到:\(b[v] = u\)\(u\)\(v\) 的正下方),从 \(S\) 中移除 \(v\)match++
      • \(u\) 加入 \(S\)\(u\) 等待被更左侧的卡片匹配为其正下方)
    • 计算答案\(K = N - match\)(每匹配一对减少一个堆),输出 \(K\)\(b[1..N]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N)\),每次 multiset 操作 \(O(\log N)\)
    • 空间复杂度:\(O(N)\),存储数组和集合
  5. 逆序贪心匹配的核心思想

    • 问题转化:最小堆数等价于最大化"卡片放在已有堆顶"的次数,即最大化匹配对数。每对匹配减少一个堆
    • 逆序处理的合理性:从右向左处理时,右侧卡片已确定是否被匹配。当前卡片 \(u\) 作为"下方卡片",只能被左侧卡片使用。逆序保证决策时右侧状态已确定
    • 贪心匹配的最优性:对当前 \(u\),选择数字最大且 \(\leq A_u\)\(v\) 进行匹配,为后续更小数字的卡片保留更多匹配机会(类似 Dilworth 分解中的贪心策略)
    • multiset 的精确查找upper_bound 配合前移,精确找到"不超过当前值的最大值",满足 \(x \geq y\) 的堆顶约束
    • \(B_i\) 最大化的隐含实现:每次匹配都产生一个非零 \(B_v\),最大化匹配数即最大化 \(\sum B_i\)
    • 适用于"序列划分成最少单调不降子序列"的变形问题(Dilworth 定理的对偶),核心在于逆序贪心将划分问题转化为匹配问题

【算法标签】

贪心

【代码详解】

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> PII;  // PII: (卡片上的数字, 卡片编号)

const int N = 200005;  // 卡片数量上限

int n;        // 卡片总数
int a[N];     // a[i]: 卡片 i 上写的整数
int b[N];     // b[i]: 卡片 i 的正下方卡片编号,0 表示在堆底

multiset<PII> S;  // 维护当前可被匹配(即处于堆顶)的卡片,按 (数字, 编号) 排序

int main()
{
    cin >> n;  // 输入卡片数量

    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];  // 输入每张卡片上的数字
    }

    int match = 0;  // 记录成功匹配的对数(即非堆底卡片的数量)

    // 从右往左处理每张卡片
    // 贪心策略:对当前卡片 u,尝试找到一个已处理的卡片 v(在 u 右侧),
    // 使得 a[v] <= a[u],即 v 可以放在 u 的上方(u 成为 v 的正下方卡片)
    for (int u = n; u >= 1; u--)
    {
        // 查找第一个严格大于 {a[u], n+1} 的位置,再前移一位
        // 即找到 S 中值最大的、且数字 <= a[u] 的卡片
        auto it = S.upper_bound({a[u], n + 1});

        if (it != S.begin())
        {
            --it;                // 前移到第一个 <= a[u] 的元素
            int v = it->second;  // v 是可被 u 放在下方的卡片编号
            b[v] = u;            // v 是可放在 u 上方的卡片编号(u 作为 v 的正下方)
            S.erase(it);         // v 已确定正下方为 u,从待匹配集合中移除
            match++;             // 匹配数加一
        }

        // u 的正下方尚未确定,加入待匹配集合
        S.insert({a[u], u});
    }

    // 最小堆数 = 总卡片数 - 匹配数(每个匹配减少一个堆)
    int k = n - match;

    cout << k << endl;  // 输出最小堆数

    for (int i = 1; i <= n; i++)
    {
        cout << b[i] << " ";  // 输出每张卡片的正下方卡片编号
    }

    cout << endl;

    return 0;
}

【运行结果】

5
3 1 4 2 2
2
0 1 0 3 4
posted @ 2026-08-06 21:58  团爸讲算法  阅读(7)  评论(0)    收藏  举报