【题解 P2048】 超级钢琴
[NOI2010] 超级钢琴
题目描述
小 Z 是一个小有名气的钢琴家,最近 C 博士送给了小 Z 一架超级钢琴,小 Z 希望能够用这架钢琴创作出世界上最美妙的音乐。
这架超级钢琴可以弹奏出 \(n\) 个音符,编号为 \(1\) 至 \(n\)。第 \(i\) 个音符的美妙度为 \(A_i\),其中 \(A_i\) 可正可负。
一个“超级和弦”由若干个编号连续的音符组成,包含的音符个数不少于 \(L\) 且不多于 \(R\)。我们定义超级和弦的美妙度为其包含的所有音符的美妙度之和。两个超级和弦被认为是相同的,当且仅当这两个超级和弦所包含的音符集合是相同的。
小 Z 决定创作一首由 \(k\) 个超级和弦组成的乐曲,为了使得乐曲更加动听,小 Z 要求该乐曲由 \(k\) 个不同的超级和弦组成。我们定义一首乐曲的美妙度为其所包含的所有超级和弦的美妙度之和。小 Z 想知道他能够创作出来的乐曲美妙度最大值是多少。
输入格式
输入第一行包含四个正整数 \(n, k, L, R\)。其中 \(n\) 为音符的个数,\(k\) 为乐曲所包含的超级和弦个数,\(L\) 和 \(R\) 分别是超级和弦所包含音符个数的下限和上限。
接下来 \(n\) 行,每行包含一个整数 \(A_i\),表示按编号从小到大每个音符的美妙度。
输出格式
输出只有一个整数,表示乐曲美妙度的最大值。
样例 #1
样例输入 #1
4 3 2 3
3
2
-6
8
样例输出 #1
11
提示
样例解释
共有 \(5\) 种不同的超级和弦:
- 音符 \(1 \sim 2\),美妙度为 \(3+2=5\);
- 音符 \(2 \sim 3\),美妙度为 \(2+(-6)=-4\);
- 音符 \(3 \sim 4\),美妙度为 \((-6)+8=2\);
- 音符 \(1 \sim 3\),美妙度为 \(3+2+(-6)=-1\);
- 音符 \(2 \sim 4\),美妙度为 \(2+(-6)+8=4\)。
最优方案为:乐曲由和弦 \(1,3,5\) 组成,美妙度为 \(5+2+4=11\)。
所有数据满足:\(-1000 \leq A_i \leq 1000\),\(1 \leq L \leq R \leq n\) 且保证一定存在满足要求的乐曲。
解题思路
题意简述就是要我们选出 \(k\) 个长度 \(l\) 在 \([L,R]\) 之间的区间,每个区间的贡献是内部数的总和,要总贡献最大。
由于有区间和,我们就可以用前缀和。
对于一个左端点为 \(l\) ,右端点为 \(r\) 的区间,它的贡献就位 \(a_r-a_{l-1}\) 。
由于有长度限制,我们可以固定住左端点为 \(l\) ,那么右端点 \(r\) 的范围就是 \([l+L-1,min(l+R-1,n)]\) 。
由于 \(l\) 不变,\(a_{l-1}\) 也不变,我们只需找出使 \(a_x\) 最小且在 \([l+L-1,min(l+R-1,n)]\) 之间的 \(x\) 即可。
那我们就可以开一个 \(ST\) 表维护 \([l+L-1,min(l+R-1,n)]\) 内的最大值,把每一个 \(l\) 以及它的右端点范围开一个结构体,丢到一个堆里面,每次贪心取最大即可。
每次取出来后,把它分裂为两部分,即分裂成 \((l,i+l-1,x-1)\) 与 \((l,x+1,min(l+R-1,n))\) ,在丢进堆即可。
每次累加答案,即可求最大值。
\(ST\) 表查询时间 \(O(1)\) ,总时间复杂度 \(O(nlogn)\) 。
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
int x,y,z;
datay(int q,int w,int e)
{
x=q;
y=w;
z=e;
}
};
long long n,a[500005],l[500005],p[500005],L,R,k,s;
int f[500005][21],z;
int gaia(int ll,int rr)
{
z=l[rr-ll+1];
return (a[f[ll][z]]>a[f[rr-p[z]+1][z]])?f[ll][z]:f[rr-p[z]+1][z];
}
bool operator <(const datay &q,const datay &w)
{
return (a[gaia(q.y,q.z)]-a[q.x])<(a[gaia(w.y,w.z)])-a[w.x];
}
priority_queue<datay> t;
int main()
{
datay q=datay(0,0,0);
int x;
scanf("%lld%lld%lld%lld",&n,&k,&L,&R);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]),f[i][0]=i,a[i]+=a[i-1];
l[0]=-1,p[0]=1;
for(int i=1;i<=n;i++)l[i]=l[i/2]+1;
for(int i=1;i<=20;i++)p[i]=p[i-1]*2;
for(int i=1;i<=20;i++)
{
for(int j=1;j+p[i]-1<=n;j++)
{
if(a[f[j][i-1]]>a[f[j+p[i-1]][i-1]])f[j][i]=f[j][i-1];
else f[j][i]=f[j+p[i-1]][i-1];
}
}
for(int i=1;i<=n-L+1;i++)
{
t.push(datay(i-1,i+L-1,min(n,i+R-1)));
}
for(int i=1;i<=k;i++)
{
q=t.top();
x=gaia(q.y,q.z);
s+=(a[x]-a[q.x]);
t.pop();
if(x!=q.y)t.push(datay(q.x,q.y,x-1));
if(x!=q.z)t.push(datay(q.x,x+1,q.z));
}
cout<<s;
return 0;
}