【洛谷P2048】超级钢琴【堆】【主席树】

题目大意:

题目链接:https://www.luogu.org/problemnew/show/P2048
一个长度为nn的序列,求mm个长度在[l,r][l,r]之间的子序列,使得这些子序列的元素之和最大。


思路:

显然暴力是很难搞的,考虑先用前缀和。
那么我们要求的就是
i=1mmax(sum[k]sum[x1])(x+l1kx+r1)\sum^{m}_{i=1}max(sum[k]-sum[x-1])(x+l-1\leq k\leq x+r-1)
考虑拆成mm次询问。
对于每一次询问,我们要求max(sum[k]sum[x1])max(sum[k]-sum[x-1])。显然对于固定的xxsum[x1]sum[x-1]也是固定的。所以我们只要求出max(sum[k])max(sum[k])即可。
kk是有范围要求的。当左端点为xx时,右端点的区间是固定的。我们每次要在这个区间内求最大,然后再求次大,接下来求第三大。
这显然就是一个静态区间第kk大的问题。直接主席树搞定就可以了。
那么我们要把所有的点作为左端点,然后求出相应的右端点,并插入堆里。每次取最大值然后维护一下就可以了。
其实这一步就是序列合并了。里面会讲解的更详细一些。
这样初始化O(nlogn)O(n\log n),每次询问O(logn)O(\log n),共mm次询问,总时间复杂度就是O(mlogn)O(m\log n)
要开long longlong\ long


代码:

#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;
}
posted @ 2019-06-29 07:42  全OI最菜  阅读(112)  评论(0编辑  收藏  举报