前往:我自己搭建的博客
题目
洛谷P2048[NOI2010]超级钢琴
题解
题目简化后即求前k大的子区间。先求前缀和s[],枚举子区间的左端点l,再找最优的右端点r(即枚举s[l-1],再找符合条件的最大的s[r]),找最大的s[r]的过程即求静态区间最大值的过程,采用ST表。将上一步找出的子区间放入优先队列中维护,接下来每次取出一个区间和最大的元素,计入答案,并求与其左端点相同,s[r]第二大的区间。求次大的s[r]时,只需先找出最大的s[r]的位置,然后在它的左侧和右侧分别用ST表求s[r]的最大值,然后都作为候选答案加入到优先队列中。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
typedef long long ll;
int n,k,L,R;
int s[maxn],f[maxn][20],lg2[maxn];
struct music{int l,rmin,rmax,max_p,sum;};
//一个music表示一个和弦,即一个子区间
//l表示区间左端点-1,max_p表示从范围[rmin,rmax]中求出的最大的s[r]的下标,sum表示区间和
priority_queue<music> q;
bool operator <(const music &x,const music &y) {return x.sum<y.sum;} //注意重载小于号
inline int read()
{
int s=0,f=1; char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
return s*f;
}
inline void get_ST() //维护最值,改成维护最值的下标
{
for(int i=1;i<=n;i++) f[i][0]=i;
for(int i=2;i<=n;i++) lg2[i]=lg2[i>>1]+1;
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f[i][j]=s[f[i][j-1]]>s[f[i+(1<<(j-1))][j-1]] ? f[i][j-1] : f[i+(1<<(j-1))][j-1];
}
inline int RMQ(int l,int r)
{
int t=lg2[r-l+1];
return s[f[l][t]]>s[f[r-(1<<t)+1][t]] ? f[l][t] : f[r-(1<<t)+1][t];
}
inline void solve()
{
for(int i=0;i+L<=n;i++) //枚举区间左端点-1(s[l-1])
{
int l=i+L,r=min(i+R,n); //此处l,r表示区间右端点的位置范围
q.push((music){i,l,r,RMQ(l,r),s[RMQ(l,r)]-s[i]});
}
ll ans=0;
for(int i=1;i<=k;i++)
{
music x=q.top(); q.pop(); ans+=(ll)x.sum;
if(x.rmin<=x.max_p-1) q.push((music){x.l,x.rmin,x.max_p-1,RMQ(x.rmin,x.max_p-1),s[RMQ(x.rmin,x.max_p-1)]-s[x.l]});
if(x.rmax>=x.max_p+1) q.push((music){x.l,x.max_p+1,x.rmax,RMQ(x.max_p+1,x.rmax),s[RMQ(x.max_p+1,x.rmax)]-s[x.l]});
} //注意判断合法性
printf("%lld\n",ans);
}
int main()
{
n=read(),k=read(),L=read(),R=read();
for(int i=1;i<=n;i++) s[i]=s[i-1]+read();
get_ST(); solve();
return 0;
}