P2048 [NOI2010] 超级钢琴 题解

前言

solve

  • 我们考虑 \(k=1\),这个情况就是只找一个数
  • sbmqwm:说了滚木
  • 我们令一个左端点为 \(i\),那其长度范围在 \([L+i-1,min(R+i-1,n)]\),令 \(s\) 表示整个序列的前缀和,设最优解下标为 \(v\),那么我们的最优解为:
$s[v]-s[i-1],(L \le v \le R)$
  • 其实最优解就是在 \([L+i-1,min(R+i-1,n)]\) 的前缀数组最大值的位置
  • 这个 \(v\) 可以用 \(ST\)\(O(1)\) 求出,\(ST\) 表维护前缀最大值就行
  • 当然,\(v\) 的下标还要维护,这很好办,把 \(ST\) 表的数组开成 \(pair\) 就行,由于 \(pair\) 默认按第一关键字排序,所以\(ST\) 表处理时就不用管,就是别在查询的时候搞反就行
  • 我不会告诉你我搞反了的
  • 当然你也可以用 \(ST\) 表纯维护下标,然后在算的时候套一个 \(a\) 就行
  • 这样我们解决了 \(k=1\) 的情况,现在扩展
  • 我们枚举左端点,第一个最优(最大)的解一定会在这些集合中,考虑下一个最优解出现的位置
  • 会有两种情况:
    1. 在上面没被选到的集合中
    2. 在选到的这个集合中(当然,除去选到最优的点)
  • 这样就很好搞了
  • 我们直接开一个堆,向里面塞一个 \(5\) 元组,分别代表这个候选解的左端点位置,取值范围(两个),最优解出现位置,答案(前缀数组相减),按答案从大到小排序
  • sbmqwm:我有两个__
  • msjing:你没有
  • 所以我们先枚举所有左端点,算一下,全放堆里,相当于是固定左端点找右端点最优,是一种候补思想
  • 然后弹堆,取最大的,这个点会把取值范围分成两段,分别计算,加堆,重复
  • 由于每次贪心那最大,这样解最优
  • 就是要特判一些情况
    1. 弹出的候选决策的取值范围 \(l>r\),不合法,\(k\) 不消耗,跳过
    2. 弹出的候选决策的取值范围 \(l=r\),合法但是加堆里会使答案错掉,以防万一我们加上贡献,\(k\) 消耗,不加堆,跳过
  • 我不会告诉你我在算第一种情况时把 \(k--\) 了的
  • 其他没啥了
  • 这题是个比较常见的 \(trick\)今天模拟赛还打到了但是msjing认为是用 CDQ + 主席树,想通了很容易了
  • 代码里的 \(freopen\) 是打模拟赛剩的忘删了
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define fi first
#define se second
using namespace std;
constexpr int maxn=5e5+10;
int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0' || ch>'9')
    {
        if (ch == '-') f=-1;
        ch=getchar();
    }
    while (ch>='0' && ch<='9')
    {
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int n,k,L,R;
int a[maxn];
namespace ST
{
    int lg[maxn];
    pair<int,int> f[maxn][20];
    void Log() {for (int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;}
    void st()
    {
        for (int j=1;j<=lg[n];j++)
            for (int i=1;i<=n-(1<<j)+1;i++)
                f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
    }
}using namespace ST;
struct _
{
    int id,l,r,mxid,sum;
    bool operator < (const _ &a) const
    {
        return sum<a.sum;
    }
};
priority_queue<_> q;
signed main()
{
    // freopen("B.in","r",stdin);
    // freopen("B.out","w",stdout);
    n=read(),k=read(),L=read(),R=read();
    for (int i=1;i<=n;i++) a[i]=read();
    for (int i=1;i<=n;i++) f[i][0].fi=f[i-1][0].fi+a[i],f[i][0].se=i;
    Log(),st();
    for (int i=1;i<=n;i++)
    {
        int l=L+i-1,r=min(R+i-1,n);
        // cerr << l << " " << r << endl;
        if (l>n) break;
        int k=lg[r-l+1];
        pair<int,int> v=max(f[l][k],f[r-(1<<k)+1][k]);
        // cerr << v.fi << " " << v.se << endl;
        q.push({i,l,r,v.se,v.fi-f[i-1][0].fi});
    }
    int ans=0;
    while (k)
    {
        _ nd=q.top();q.pop();
        // cerr << nd.sum << endl;
        if (nd.l>nd.r) continue;
        ans+=nd.sum;
        k--;
        if (nd.l == nd.r) continue;
        // cerr << nd.id << " " << nd.l << " " << nd.r << 
        // " " << nd.mxid << " " << nd.sum << endl;
        int l1=nd.l,r1=nd.mxid-1,l2=nd.mxid+1,r2=nd.r;
        int k1=lg[r1-l1+1],k2=lg[r2-l2+1];
        pair<int,int> v1=max(f[l1][k1],f[r1-(1<<k1)+1][k1]),v2=max(f[l2][k2],f[r2-(1<<k2)+1][k2]);
        // cerr << "---------------" << endl;
        // cerr << v1.fi << " " << v1.se << endl;
        // cerr << v2.fi << " " << v2.se << endl;
        // cerr << "--------------" << endl;
        q.push({nd.id,l1,r1,v1.se,v1.fi-f[nd.id-1][0].fi});
        q.push({nd.id,l2,r2,v2.se,v2.fi-f[nd.id-1][0].fi});
    }
    printf("%lld\n",ans);
    return 0;
}
posted @ 2026-07-24 17:40  msjing  阅读(10)  评论(0)    收藏  举报