洛谷P9474 [yLOI2022] 长安幻世绘

题面分析

引用题面的形式化题意:在元素互不相同的数列 \(a\) 中选出一个长度为 \(m\) 的元素互不相邻的子序列,使得子列的极差最小。

想要直接选出长度为 \(m\) 的元素互不相邻的子序列有点麻烦,所以我们从极差最小入手,先给 \(a\) 数组排个序,考虑用双指针跑,跑到合适的记录答案。

现在问题来到怎么判断选择的区间是不是合适的。

我们在排序后数列中选择的连续区间内的数在计算贡献时是“可选”的,这些“可选”的数在原数列中的表现为若干段区间,而由于题目的限制“元素互不相邻”,对于原数列上长度为 \(len\) 区间最多选择 \(\frac {len + 1}{2}\) 个数。

每次双指针拓展可以使一个点由可选变成不可选或从不可选变成可选,每次要检查整个原数列上若干段区间的总贡献。这个问题就变成一个“单点修改、区间查询”的问题,就可以想到线段树。

关于具体实现

在线段树每个节点维护五个变量:可选的数个数、答案、由区间左端点开始的连续可选的数的个数、由区间右端点开始的连续可选的数的个数、区间是否是满区间。这样就可以维护线段树,每次查询根节点的答案即可。

Code

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

int rd()//快读

const int N = 1e5 + 5;

int n,m;
struct node
{
    int val,id;
    bool operator < (const node &x) const{ return val < x.val; }
}a[N];

struct sgt
{
    int sum,ans,l,r;
    bool full;
    void change()
    {
        sum = ans = l = r = sum ^ 1;
        full ^= 1;
    }
}t[N << 2];

#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

int calc(int x) { return (x + 1) / 2; }

void pu(int p)
{
    t[p].sum = t[ls].sum + t[rs].sum;
    t[p].full = t[ls].full & t[rs].full;
    if(t[ls].r && t[rs].l)
    {
        if(t[p].full)
        {
            t[p].l = t[p].r = t[p].sum;
            t[p].ans = calc(t[p].sum);
        }
        else if(t[ls].full)
        {
            t[p].l = t[ls].sum + t[rs].l;
            t[p].r = t[rs].r;
            t[p].ans = calc(t[p].l) + t[rs].ans - calc(t[rs].l);
        }
        else if(t[rs].full)
        {
            t[p].r = t[ls].r + t[rs].sum;
            t[p].l = t[ls].l;
            t[p].ans = calc(t[p].r) + t[ls].ans - calc(t[ls].r);
        }
        else
        {
            t[p].l = t[ls].l;
            t[p].r = t[rs].r;
            t[p].ans = t[ls].ans - calc(t[ls].r) + t[rs].ans - calc(t[rs].l) + calc(t[ls].r + t[rs].l);
        }
    }
    else
    {
        t[p].l = t[ls].l;
        t[p].r = t[rs].r;
        t[p].ans = t[ls].ans + t[rs].ans;
    }
}

void upd(int p,int pl,int pr,int lr)
{
    if(pl == pr) return t[p].change();
    if(lr <= mid) upd(ls,pl,mid,lr);
    else upd(rs,mid + 1,pr,lr);
    pu(p);
}

signed main()
{
    n = rd(),m = rd();
    for(int i = 1;i <= n;i++) a[i] = {rd(),i};
    sort(a + 1,a + n + 1);
    int l = 1,r = 0,ans = 0x7fffffff;
    while(1)
    {
        int f = 0;
        while(1)
        {
            r++;
            if(r > n) { f = 1;break; }
            upd(1,1,n,a[r].id);
            if(t[1].ans >= m) break;
        }
        if(f) break;
        while(1)
        {
            ans = min(ans,a[r].val - a[l].val);
            upd(1,1,n,a[l++].id);
            if(t[1].ans < m) break;
        }
    }
    cout << ans;
    return 0;
}
posted @ 2025-07-10 17:50  IC0CI  阅读(6)  评论(0)    收藏  举报