NOI2010 超级钢琴

题目链接:戳我

RMQ版本

就是我们考虑记录一个三元组qwq,一个是pos,一个是l,一个是r。
我们可以用ST表来查询固定左端点,右端点在一段区间内的最大值所在的位置。

然后我们使用优先队列,不断累加最大值,然后弹出,求它的区间的子区间内的最大值。

代码如下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define MAXN 500010
using namespace std;
int n,l,r,k,cnt;
long long ans;
int log2[MAXN],st[MAXN][21],sum[MAXN],a[MAXN];
struct Node{
    int st,l,r,maxpos;
    friend bool operator < (struct Node x,struct Node y)
        {return (sum[x.maxpos]-sum[x.st-1])<(sum[y.maxpos]-sum[y.st-1]);}
};
priority_queue<Node>q;
inline void init()
{
    for(int i=1;i<=20;i++)
    {
        for(int j=1;j+(1<<i)-1<=n;j++)
        {
            int cur1=st[j][i-1];
            int cur2=st[j+(1<<(i-1))][i-1];
            if(sum[cur1]>sum[cur2]) st[j][i]=cur1;
            else st[j][i]=cur2;
        }
    }
}
inline int query(int l,int r)
{
    if(l==r) return l;
    int cur1=st[l][log2[r-l+1]];
    int cur2=st[r-(1<<log2[r-l+1])+1][log2[r-l+1]];
    if(sum[cur1]>sum[cur2]) return cur1;
    else return cur2;
}
inline void solve()
{
    for(int i=1;i<=n;i++)
    {
        int ll=i+l-1,rr=min(n,i+r-1);
        if(ll>n) continue;
        q.push((Node){i,ll,rr,query(ll,rr)});
        // printf("%d %d %d %d\n",i,ll,rr,q.top().maxpos);
    }
    while(!q.empty()&&cnt<k)
    {
        Node u=q.top(); q.pop();
        // printf("maxpos=%d\n",u.maxpos);
        ans+=sum[u.maxpos]-sum[u.st-1];
        cnt++;
        if(u.maxpos-1>=u.l) q.push((Node){u.st,u.l,u.maxpos-1,query(u.l,u.maxpos-1)});
        if(u.maxpos+1<=u.r) q.push((Node){u.st,u.maxpos+1,u.r,query(u.maxpos+1,u.r)});
    }
    printf("%lld\n",ans);
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    scanf("%d%d%d%d",&n,&k,&l,&r);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) st[i][0]=i;
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
    // for(int i=1;i<=n;i++) printf("sum[%d]=%d\n",i,sum[i]);
    log2[0]=-1;
    for(int i=1;i<=n;i++) log2[i]=log2[i>>1]+1;
    // for(int i=1;i<=n;i++) printf("log2[%d]=%d\n",i,log2[i]);
    init();
    solve();
    return 0;
}

主席树版本

也是维护一个三元组。
因为求的是一个连续的区间和,所以我们可以先做一下前缀和,然后固定右端点,在可选区间中选择最小的即可。
就是记录一下在这个pos为右端点的区间中,已经取到了第多少qwq
维护取第K大就是一个静态区间求第K的主席树经典应用qwq
然后放进堆里即可。每次取出最大的,然后放入以这个为右端点,的K+1大值。
记得要先离散化一下qwq,还有一直取到头(也就是前缀和减0)的情况——其实就是离散化的时候往数组里面放一个0即可。

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#define MAXN 500010
using namespace std;
int n,k,L,R,top,tot;
int pre[MAXN],rt[MAXN],sum[MAXN];
long long ans;
struct T{int siz,val,ls,rs;}t[MAXN<<5];
struct Node
{
    int pos,v,cnt;
    friend bool operator < (struct Node x,struct Node y) 
        {return x.v<y.v;}
};
priority_queue<Node>q;
inline void modify(int &x,int f,int l,int r,int k)
{
    x=++tot;
    t[x]=t[f],t[x].siz++;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(k<=mid) modify(t[x].ls,t[f].ls,l,mid,k);
    else modify(t[x].rs,t[f].rs,mid+1,r,k);
}
inline int query(int x,int f,int l,int r,int k)
{
    if(l==r) return l;
    int cur=t[t[x].ls].siz-t[t[f].ls].siz;
    int mid=(l+r)>>1;
    if(k<=cur) return query(t[x].ls,t[f].ls,l,mid,k);
    else return query(t[x].rs,t[f].rs,mid+1,r,k-cur);
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    scanf("%d%d%d%d",&n,&k,&L,&R);
    n++;
    for(int i=2;i<=n;i++) 
        scanf("%d",&sum[i]),sum[i]=sum[i-1]+sum[i],pre[++top]=sum[i];
    pre[++top]=0;
    sort(&pre[1],&pre[top+1]);
    top=unique(&pre[1],&pre[top+1])-pre-1;
    for(int i=1;i<=n;i++) sum[i]=lower_bound(&pre[1],&pre[top+1],sum[i])-pre;
    for(int i=1;i<=n;i++) modify(rt[i],rt[i-1],1,top,sum[i]);
    for(int i=L+1;i<=n;i++)
    {
        int pos=query(rt[max(0,i-L)],rt[max(0,i-R-1)],1,top,1);
        q.push((Node){i,pre[sum[i]]-pre[pos],1});
    }
    while(k--)
    {
        Node now=q.top();q.pop();
        now.cnt++;
        ans+=now.v;
        // printf("ans=%lld\n",ans);
        if(now.cnt>min(max(0,now.pos-L),R-L+1)) continue;
        int cur=query(rt[max(0,now.pos-L)],rt[max(0,now.pos-R-1)],1,top,now.cnt);
        now.v=pre[sum[now.pos]]-pre[cur];
        q.push(now);
    }
    printf("%lld\n",ans);
    return 0;
}
posted @ 2019-04-09 10:39  风浔凌  阅读(115)  评论(0编辑  收藏  举报