[BZOJ2006][NOI2010]超级钢琴
sol
有一个很暴力的想法,就是把所有合法的状态丢到一个堆里面,然后依次取出最大值。这样的话时间是\(O(n^2logn)\),空间是\(O(n^2)\)
我们考虑优化这个过程。对于右端点相同的所有合法区间,我们只在堆中保留最大的一个,在取出这一个以后再丢入次大的,取出次大的再丢入次次大的,以此类推。这样可以保证正确性,同时空间被优化到了\(O(n)\)
现在我们考虑怎么查这个次次次次(这里有若干个次)大值。
首先把区间和转化成前缀和相减,因为我们固定了右端点,那么最大值就是对应了前缀和中减去的那个值最小。而第k大值刚好对应减去的那个数第k小,同时还要保证在一定区间范围内(L、R的限制)。所以离散前缀和以后主席树即可。
注意前缀和减去的部分可以是0,所以0也要被离散进去。
code
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define ll long long
const int N = 500005;
int gi()
{
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
struct node{
int i,k,val;
bool operator < (const node &yyb) const
{return val<yyb.val;}
};
struct prisident_tree{int ls,rs,num;}t[N*30];
int n,K,L,R,a[N],o[N],len,rt[N],tot;ll ans;
priority_queue<node>Q;
void modify(int &x,int l,int r,int p)
{
t[++tot]=t[x];
t[x=tot].num++;
if (l==r) return;
int mid=l+r>>1;
if (p<=mid) modify(t[x].ls,l,mid,p);
else modify(t[x].rs,mid+1,r,p);
}
int query(int A,int B,int l,int r,int k)
{
if (l==r) return l;
int sum=t[t[A].ls].num-t[t[B].ls].num,mid=l+r>>1;
if (k<=sum) return query(t[A].ls,t[B].ls,l,mid,k);
else return query(t[A].rs,t[B].rs,mid+1,r,k-sum);
}
int main()
{
n=gi();K=gi();L=gi();R=gi();n++;
for (int i=2;i<=n;i++) a[i]=gi();
for (int i=1;i<=n;i++) o[++len]=a[i]=a[i]+a[i-1];
sort(o+1,o+len+1);len=unique(o+1,o+len+1)-o-1;
for (int i=1;i<=n;i++) a[i]=lower_bound(o+1,o+len+1,a[i])-o;
for (int i=1;i<=n;i++) rt[i]=rt[i-1],modify(rt[i],1,len,a[i]);
for (int i=L+1;i<=n;i++) Q.push((node){i,1,o[a[i]]-o[query(rt[i-L],rt[max(0,i-R-1)],1,len,1)]});
while (K--)
{
node tmp=Q.top();Q.pop();
ans+=tmp.val;
if (tmp.k==min(R-L+1,tmp.i-L)) continue;
tmp.k++;
tmp.val=o[a[tmp.i]]-o[query(rt[tmp.i-L],rt[max(0,tmp.i-R-1)],1,len,tmp.k)];
Q.push(tmp);
}
printf("%lld\n",ans);
return 0;
}