正题
题目链接:https://www.luogu.org/problemnew/show/P2048
题目大意
一个长度为nnn序列aaa。寻找kkk个子序列要求长度在L∼RL\sim RL∼R之间,求这kkk个子序列的最大和。
解题思路
首先对aaa求出前缀和数组sss。题目转换为求kkk个数对要求两两之间距离在L∼RL\sim RL∼R且差最大。
因为数对之间互不影响,所以显然求前kkk大的数对就好了。
我们在大根堆之中存储一个五元组(l,r,id,x,val)(l,r,id,x,val)(l,r,id,x,val)。表示对于后面的数ididid在l∼rl\sim rl∼r之间求一个xxx使得val=aid−axval=a_{id}-a_xval=aid−ax最大。堆以valvalval为关键字。xxx和valvalval我们可以用RMQRMQRMQ快速计算出来。
然后我们开始时对于每个iii我们将(i−R,i−L,i,x,val)(i-R,i-L,i,x,val)(i−R,i−L,i,x,val)丢入堆中。
之后执行kkk次取出对顶(l,r,id,x,val)(l,r,id,x,val)(l,r,id,x,val)使ans+=valans+=valans+=val。
然后将(l,x−1,id,x′,val′)(l,x-1,id,x',val')(l,x−1,id,x′,val′)和(x+1,r,id,x′,val′)(x+1,r,id,x',val')(x+1,r,id,x′,val′)重新丢入堆中,这样就保证了对于不同的ididid,xxx不会重复而且也能取到最大。
时间复杂度:O(nlog(n+k)):O(n\ log\ (n+k)):O(n log (n+k))
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define ll long long
using namespace std;
const ll N=501000;
ll n,k,L,R,lg[N],f[30][N],a[N],ans,w[30][N];
ll RMQ(ll l,ll r)
{ll z=lg[r-l+1];return f[z][l]<f[z][r+1-(1<<z)]?w[z][l]:w[z][r+1-(1<<z)];
}
struct node{ll l,r,x,id,val;node(ll _l=0,ll _r=0,ll _id=0){l=_l;r=_r;id=_id;x=RMQ(l,r);val=a[id]-a[x];}
};
bool operator <(const node &a,const node &b)
{return a.val<b.val;}
priority_queue<node> q;
int main()
{scanf("%lld%lld%lld%lld",&n,&k,&L,&R);lg[0]=-1;for(ll i=1;i<=n;i++){scanf("%lld",&a[i]);a[i]+=a[i-1];f[0][i]=a[i];w[0][i]=i;lg[i]=lg[i/2]+1;}for(ll i=1;(1<<i)<=n;i++)for(ll j=0;j+(1<<i)-1<=n;j++){if(f[i-1][j+(1<<i-1)]<f[i-1][j])w[i][j]=w[i-1][j+(1<<i-1)];elsew[i][j]=w[i-1][j];f[i][j]=min(f[i-1][j],f[i-1][j+(1<<i-1)]);}for(ll i=L;i<=n;i++)q.push(node(max(i-R,0ll),i-L,i));while(k--){node z=q.top();ans+=z.val;q.pop();if(z.x>z.l) q.push(node(z.l,z.x-1,z.id));if(z.x<z.r) q.push(node(z.x+1,z.r,z.id));}printf("%lld",ans);
}