[题解] Codeforces Round 1130 (Div. 2)C. Yet Another Black Cell Problem

题目:

受此题启发。

有一条被划分为单元格的长条,单元格从左到右编号为 $0$ 到 $10^9$。初始时,所有单元格都是白色的。

给定一个长度为 $n$ 的数组 $a$ ,其中包含 $n$ 个单元格的下标。我们希望将这 $n$ 个单元格全部染黑。

给定一个整数 $x$,你可以选择一个整数 $y$,满足 $0 \le y \le x$,然后任意选择 $y$ 个不在 $a$ 中的单元格。同时,我们也希望在保持所有未被选择的单元格为白色的情况下,将这选出的 $y$ 个单元格染黑。

你可以任意多次执行以下操作:

  • 选择两个不同的单元格 $i$ 和 $j$,满足 $1 \le |i-j| \le k$,并将它们染黑,无论它们原本是黑色还是白色。

求能够将所有需要染黑的单元格染黑,同时保持其他单元格为白色的最小 $k$。

每个测试包含多个测试用例。第一行包含测试用例数 $t(1≤t≤10^4)$。测试用例的描述如下。

每个测试用例的第一行包含两个整数 n 和$ x(2≤n≤2⋅10^5,0≤x≤n)$。

第二行包含 n 个整数$ a_1,a_2,…,a_n(0≤a_i≤10^9)$。保证对于所有 $1≤i<n$,都有 $a_i<a_i+1$。

保证所有测试用例中 n 的总和不超过 $2⋅10^5$。

这是一道二分查找,二分划定染色的区块长度再核实是否可行

1.误区:为什么不能贪心堆查找

错误写法:将整个序列计算相邻距离并存入大根堆中,每次优化最长距离至原距离的1/2

题目中说明了选择距离在k以内的两个格子(不要求颜色)染为黑色,所以每个格子可以复用且黑格也可以作为起点

而对于每个格子,可以由左/右任意一侧的黑格子作为落脚点转化

所以并不是所有格子都是从距离中间向两侧优化的,这种解法只看了一个方向

2.二分答案实现

对于一个黑格$i$:当$i$两侧存在距离$<=k$的相邻黑格时认为这个黑格是相邻的,若左侧距/右侧距存在且$>k$则认为其是"孤立"的

对于孤立点i:

  • 若$i<n-1$且$pos[i+1]-pos[i]<=2*k$(这个距离内可优化间距至k以内),则染色一次可以优化两侧黑格的最小相邻距离,i+=2

  • 若$pos[i+1]-pos[i]>2*k$,则染色仅能优化这一个孤立点距离,i++

原题$n<2e^5$且k增大使用的优化染色个数单调减少,可以考虑二分取k值并统计使用染色的次数是否$<x$

3.源代码:

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

//思路:二分答案

void solve(){
    int n,x;
    cin>>n>>x;
    vector<int> pre(n,0);
    for(int i=0;i<n;i++){
        cin>>pre[i];       
    }
    
    vector<int> signle_inx;		//装载孤立点
    auto check=[&](int mid)->bool{
        for(int i=0;i<n;i++){
            //确定一点是否为孤立点 注意:一定要两侧都判定
            if((i==0||pre[i]-pre[i-1]>mid)&&(i==n-1||pre[i+1]-pre[i]>mid)){
                signle_inx.push_back(i);
            }
        }
        if(signle_inx.empty()) return true;	//没有孤立点,说明已实现
        int sz=signle_inx.size(),used=0;
        for(int i=0;i<sz;){
            if(pre[signle_inx[i]]-pre[signle_inx[i-1]]<=2*mid){//可以同时优化两个点
                i+=2;
            }else{//仅能优化当前点
                i++;
            }
            used++;
        }
        signle_inx.clear();	//清空便于下轮使用
        return used<=x;
    };
    
    int l=0,r=1e9;//二分答案,上界直接定1e9(方便)
    while(r-l>1){
        int mid=l+(r-l)/2;
        if(check(mid)){
            r=mid;
        }else{
            l=mid;
        }
    }
    cout<<r<<'\n';
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
    cin>>t;
    while(t--){
        solve();
    }
    return 0;
}
posted @ 2026-11-11 14:51  yiliyu  阅读(3)  评论(0)    收藏  举报