[题解] 2025CCPC南昌邀请赛 H. QOJ 14508. 贪心

[题解] 2025CCPC南昌邀请赛 H. QOJ 14508. 贪心

TAG: 贪心, 构造
题目链接: 点击此处打开题目

题目大意

给定 \(n\) 颗珍珠(或者某种需求序列),每颗珍珠在最终要求达到的计数值为 \(a[i]\)。我们需要决定每次操作放入哪些珍珠或对珍珠进行全体操作,以求最少的操作次数来满足数组的要求。(此为推演意图,根据代码逻辑抽象为:每个位置 \(i\) 的元素有一个最晚上限要求,且必须随着下标严格递增排布,在安排好位置之后剩余量会作为需求向后传递)。如果无法满足则输出 -1

核心思路

通过给出的代码可以发现,题目对序列的操作其实是一个依赖推导的贪心放入过程。

  1. 倒序求上限限制 (lim)
    数组中排在后面的元素会挤压前面元素的最大生效位号,即第 \(i\) 颗珍珠放入的操作编号一定小于第 \(i+1\) 颗的要求。因此我们倒序推导每一个位置能够放置的最晚合法位置(限界)
    \(lim[i] = \min(a[i], lim[i+1] - 1)\)。
    如果在任何位置出现 \(lim[i] < i\),说明前面的空间连从 1 开始挨个放都不够,必然冲突,直接输出 -1
  2. 正序贪心分配 (last & rem)
    确定了所有元素的上限后,我们需要尽量把元素往靠后的地方放(能拖就拖),以便于统一处理后续元素增加剩余次数。
    last 为上一个元素排入的操作编号,rem 代表当前所有已放入元素还需要被“附带增加”的总次数。
    如果考虑把第 \(i\) 个元素放在第 \(x\) 次操作,根据条件:在上一个元素放入后的操作里,所有中间的空缺操作都会消耗我们的剩余未加名额(即 x - last - 1)。
    所以我们能贪到的最大位置受限于 rem 的值和事先求好的 lim[i]:\(x = \min(lim[i], last + rem + 1)\)。
    最后更新 rem 的值:扣除前面填补空隙用掉的,然后加上当前元素还需要的步数。
  3. 统计结果
    所有元素都安放完毕后,最后的基础操作数 last 加上依然剩下的 rem 就是需要进行的最少总操作数。

复杂度分析

  • 空间复杂度:需要两个大小为 \(N\) 的一维数组 alim,内存占用约为 \(O(N)\)。
  • 时间复杂度:每一组数据只需遍历两次数组,一次倒序求最晚安全限制,一次正序贪心计算操作位号,计算都只有基础运算。单组数据时间复杂度为 \(O(N)\),非常高效。

原理解析与 AC 代码

  • a[i]: 每个元素要求的最大计数值限制。
  • lim[i]: 预处理出的第 \(i\) 个元素可以被安排的最晚有效编号。
  • lastrem: last 表示上一次操作实际锁定的编号,rem 是手上还握着用来填补差值余量的需要再进行的全体加操作数。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n, a[N], lim[N];

void solve(){
    cin>>n;
    for(int i=1; i<=n; i++) cin>>a[i];
    lim[n]=a[n];
    // 1. 倒序求放置上限,由于位置递增所以前面必须小于后面
    for(int i=n-1; i>=1; i--){
        lim[i]=min(a[i], lim[i+1]-1);
    }
    // 2. 如果上限连最基本的顺序排列(1到i)都装不下,显然无解
    for(int i=1; i<=n; i++){
        if(lim[i]<i){
            cout<<-1<<endl;
            return;
        }
    }
    
    int last=0; // 上一次放入操作的编号
    int rem=0;  // 当前已经放入的元素,后面还需要被加多少次
    
    // 3. 正序贪心分配
    for(int i=1; i<=n; i++){
        // 考虑把第 i 颗珍珠放在第 x 次操作,
        // (last+1)->(x-1) 都是已有的共同 +1 次数
        // rem >= x - last - 1 等价于 x <= last + rem + 1
        int x=min(lim[i], last+rem+1);
        rem-=x-last-1; // 减去中间被作为空余操作填上的消耗
        rem+=a[i]-x;   // 加上当前珍珠在 x 处生成后,剩余还要补的量
        last=x;        // 更新位置
    }
    cout<<last+rem<<endl;
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t; cin>>t;
    while(t--) solve();
    return 0;
}
posted @ 2026-05-19 18:34  艾拉别哭  阅读(15)  评论(0)    收藏  举报