【洛谷P2048】超级钢琴【堆】【主席树】
题目大意:
题目链接:https://www.luogu.org/problemnew/show/P2048
一个长度为的序列,求个长度在之间的子序列,使得这些子序列的元素之和最大。
思路:
显然暴力是很难搞的,考虑先用前缀和。
那么我们要求的就是
考虑拆成次询问。
对于每一次询问,我们要求。显然对于固定的,也是固定的。所以我们只要求出即可。
而是有范围要求的。当左端点为时,右端点的区间是固定的。我们每次要在这个区间内求最大,然后再求次大,接下来求第三大。
这显然就是一个静态区间第大的问题。直接主席树搞定就可以了。
那么我们要把所有的点作为左端点,然后求出相应的右端点,并插入堆里。每次取最大值然后维护一下就可以了。
其实这一步就是序列合并了。里面会讲解的更详细一些。
这样初始化,每次询问,共次询问,总时间复杂度就是。
要开。
代码:
#include <map>
#include <cmath>
#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>
#define mp make_pair
using namespace std;
typedef long long ll;
priority_queue<pair<ll,int> > q;
const int N=500010,M=500010*25;
int n,m,L,R,T,tot,root[N],num[N];
ll sum[N],b[N],ans;
struct Tree
{
int ls,rs,cnt;
}tree[M];
int build(int l,int r)
{
int p=++tot;
if (l==r) return p;
int mid=(l+r)/2;
tree[p].ls=build(l,mid);
tree[p].rs=build(mid+1,r);
return p;
}
int insert(int now,int l,int r,int k)
{
int p=++tot;
tree[p]=tree[now];
tree[p].cnt++;
if (l==r) return p;
int mid=(l+r)/2;
if (k<=mid) tree[p].ls=insert(tree[p].ls,l,mid,k);
else tree[p].rs=insert(tree[p].rs,mid+1,r,k);
return p;
}
int ask(int x,int y,int l,int r,int k)
{
if (l==r) return l;
int mid=(l+r)/2,sum=tree[tree[y].ls].cnt-tree[tree[x].ls].cnt;
if (sum>=k)
return ask(tree[x].ls,tree[y].ls,l,mid,k);
else return ask(tree[x].rs,tree[y].rs,mid+1,r,k-sum);
}
//以上主席树板子
int main()
{
scanf("%d%d%d%d",&n,&m,&L,&R);
for (int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
sum[i]=sum[i-1]+x;
b[i]=sum[i];
}
sort(b+1,b+1+n);
T=unique(b+1,b+1+n)-b-1;
root[0]=build(1,T);
for (int i=1;i<=n;i++)
root[i]=insert(root[i-1],1,T,lower_bound(b+1,b+1+T,sum[i])-b);
for (int i=1;i<=n;i++)
{
int l=i+L-1,r=min(i+R-1,n);
if (l>n) break;
ll x=b[ask(root[l-1],root[r],1,T,(r-l+1))];
q.push(mp(x-sum[i-1],i));
num[i]=1;
}
while (m--)
{
ans+=q.top().first;
int i=q.top().second;
q.pop();
num[i]++;
int l=i+L-1,r=min(i+R-1,n);
if (l<=n&&num[i]<=r-l+1)
{
ll x=b[ask(root[l-1],root[r],1,T,(r-l+1-num[i]+1))];
//静态区间第k大=静态区间第r-l+1-k+1小
q.push(mp(x-sum[i-1],i));
}
}
printf("%lld\n",ans);
return 0;
}