「CF 883I」Photo Processing 题解

题目传送门

前置知识:二分、DP、差分、双指针

题目大意

将一个序列 \(v_1, v_2, ... , v_n\) 分成若干组,每组至少k个,求出每组最大值与最小值之差(极差(chā))中的最小值

极差(chā):一组数据中‌最大值与最小值的差

题目分析

题目要求在最大的里面找最小的,且如果极差限制为 X 时能成功分组,那么极差限制为 \(X+1\) 时(更宽松了)也一定能成功分组,考虑二分

Part 1:check函数

对于一个极差限制 \(X\) ,我们需要判断在这样的限制下能否成功分组

首先排序

贪心?假设我们每次对于一个数如果能分到上一个组就尽可能分到上一个组,否则就再开一个组,然后判断长度是否大于等于k

反例:\(n=7, k=3, a=[1,2,3,4,10,11,12], X=9\)

嘶……似乎不可行啊……

贪心在分组时总是尽量延长当前组,但实际上可以选择提前结束当前组,以便为后面的组保留足够的元素

——考虑贪心的好盆友DP

为什么是DP?

我们发现这是一个分段问题。分段问题的核心通常是:前面怎么分,不影响后面怎么分,也就是只要我知道前面分到了哪里——DP直觉来源

考虑后效性:

什么叫后效性?就是前面怎么分,会不会影响后面的选择?

在这个问题里:

• 如果你已经分好了前 i 个元素,后面从 i+1 开始重新分。

• 前面分了几个组、每组怎么分的,完全不影响 i+1 往后怎么分。

• 唯一需要知道的信息就是:当前分到了哪个位置。

这就是无后效性——满足 DP 的必要条件。

Part2:如何设计DP

状态定义:我们定义状态 \(dp[i]=1/0\) 表示前i个元素是/否可以被完整分组

状态边界

\(dp[0]=1\)

显然什么都没有的时候我们可以不分组,同样合法

状态转移

对于每个 \(i(dp[i]=1)\) ,我们从 \(i+1\) 开始重新分组,所以我们需要找到一个下一组的结束位置 \(j\) ,需要满足:

  • \(j \geq k+i\)

  • \(a[j]-a[i] \leq X\) (注意此时数组已经排好了序)

我们定义:\(r[i]\) 表示从第 \(i\) 个数开始,能找到的极差不大于 \(X\) 的最远结束位置 \(j\) ,此时可以使用双指针 \((O(n))\) 或二分 \((O(n log n))\) 求解

同时,我们需要满足\(j \geq k+i\),那么就可推出可以结束位置 \(j\) 的取值范围为 \([i+k,r[i+1]]\)

所以,对于 \(j \in [i+k,r[i+1]]\) ,dp值均可以被赋为1

我们每次需要知道 \(dp[i]\) 的值。那么这其实就是一个区间修改和单点查询问题

Part 3:差分优化DP

对于DP所需的区间查询和单点修改操作,我们可以使用差分来优化

同时,我们需要知道当前的 \(dp[i]\) 是否为1,这样才能进行下一步的转移

也就是我们需要知道差分数组下标 \(i\) 以前的前缀和是否为1。

已知我们每次修改的区间位置都一定大于 \(i\),那么当我们处理到 \(i\) 时,后面的修改区间一定不会再更新到 \(i\)

所以我们动态维护差分数组前缀和即可

Part 4:炸飞作者的细节

  • 差分边界:对于 \(a[1]\) 要特殊处理

  • 特判修改区间 \(L > R\) 的特殊情况

Part 5:总结

分段问题的本质是前缀可达性传递:只要知道哪些前缀可以完美分段,就能推出后续所有可能的分割点,贪心之所以失效,是因为它只留一条路,而DP用区间标记保留了所有可能情况。

代码实现

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

const int MAXN = 3e5 + 10;

int a[MAXN];
int n, k;
int r[MAXN];
int d[MAXN];
int dp[MAXN]; 

bool check(int x) {
    memset(r, 0, sizeof(r));

    // 计算每个位置 i 能覆盖的最远右端点 r[i]
    for (int i = 1, j = 1; i <= n && j <= n; i++) {
        if (j < i) j = i;
        while (j + 1 <= n && a[j + 1] - a[i] <= x) j++;
        r[i] = j;
    }

    memset(d, 0, sizeof(d));
    int s = 0;

    if (r[1] >= k) {
        d[k]++;
        d[r[1] + 1]--;
    }

    for (int i = 1; i <= n; i++) {
        s += d[i];// 动态维护前缀和
        if (s > 0) {
            int L = i + k;
            int R = r[i + 1];
            if (L > R) continue;
            d[L]++;
            d[R + 1]--;
        }
    }

    return s > 0;
}

int binary_answer(int l, int r) {
    while (l < r) {
        int mid = (l + r) >> 1;
        if (f(mid))
            r = mid;
        else
            l = mid + 1;
    }
    return l;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];
    sort(a + 1, a + 1 + n);
    cout << binary_answer(0, 1000000000) << '\n';
    return 0;
}

鸣谢:

Deepseek

The End

欢迎Debug!

posted @ 2026-08-10 21:01  一叶与  阅读(21)  评论(0)    收藏  举报