bzoj4504: K个串【主席树区间操作+优先队列】

本文介绍了一种利用主席树解决特定区间和问题的方法,即在一个数字序列的所有连续子串中找到第K大的不重复元素之和。通过主席树维护区间最大值并结合堆数据结构实现高效查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

兔子们在玩k个串的游戏。首先,它们拿出了一个长度为n的数字序列,选出其中的一
个连续子串,然后统计其子串中所有数字之和(注意这里重复出现的数字只被统计一次)。
兔子们想知道,在这个数字序列所有连续的子串中,按照以上方式统计其所有数字之和,第
k大的和是多少。

Input

第一行,两个整数n和k,分别表示长度为n的数字序列和想要统计的第k大的和
接下里一行n个数a_i,表示这个数字序列

Output

一行一个整数,表示第k大的和

Sample Input

7 5

3 -2 1 2 2 1 3 -2

Sample Output

4

HINT

1 <= n <= 100000, 1 <= k <= 200000, 0 <= |a_i| <= 10^9数据保证存在第 k 大的和.

解题思路:

考虑主席树,第i棵树节点(l,r)维护右端点为i,左端点在(l,r)之间的区间的最大值。

由于不计重复的数,所以每新加一个数ai,相当于在第i-1棵树的(pre[i],i)区间加了ai,所以要区间打标记,主要是注意主席树打标记时要新建节点,否则前面的树的节点也会指向这里。

然后如何维护总体第k大呢?
claris的做法:用一个大根堆维护五元组(v,x,l,r,p),表示区间和为v,右端点i所在线段树根节点为x,所选左端点范围为[l,r],左端点选了p。
开始时只把每个根节点的值求进堆,然后重复k次,每次取出堆顶,扩展出[l,m−1]以及[m+1,r]两个新状态。

#include<bits/stdc++.h>
#define ll long long
#define P pair<ll,int>
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=100005,M=7000005;
int n,m;
int tot,rt[N],lc[M],rc[M];P v[M];ll tag[M];
map<int,int>pre;
struct node
{
    ll v;int x,l,r,p;
    node(){}
    node(ll _v,int _x,int _l,int _r,int _p):v(_v),x(_x),l(_l),r(_r),p(_p){}
    inline friend bool operator < (const node &a,const node &b){return a.v<b.v;}
}tmp;
priority_queue<node>q;

int build(int l,int r)
{
    int x=++tot;v[x]=P(0,l);
    if(l==r)return x;
    int mid=l+r>>1;
    lc[x]=build(l,mid),rc[x]=build(mid+1,r);
    return x;
}
int add(int y,ll p)
{
    int x=++tot;
    lc[x]=lc[y],rc[x]=rc[y],v[x]=v[y],v[x].first+=p,tag[x]=tag[y]+p;
    return  x;
}
void pushdown(int x)
{
    lc[x]=add(lc[x],tag[x]),rc[x]=add(rc[x],tag[x]);
    tag[x]=0;
}
int modify(int y,int l,int r,int ql,int qr,int p)
{
    if(ql<=l&&r<=qr)return add(y,p);
    if(tag[y])pushdown(y);
    int x=++tot;lc[x]=lc[y],rc[x]=rc[y],v[x]=v[y];
    int mid=l+r>>1;
    if(ql<=mid)lc[x]=modify(lc[y],l,mid,ql,qr,p);
    if(qr>mid)rc[x]=modify(rc[y],mid+1,r,ql,qr,p);
    v[x]=max(v[lc[x]],v[rc[x]]);
    return x;
}
P query(int x,int l,int r,int ql,int qr)
{
    if(ql==l&&qr==r)return v[x];
    if(tag[x])pushdown(x);
    int mid=l+r>>1;
    if(qr<=mid)return query(lc[x],l,mid,ql,qr);
    else if(ql>mid)return query(rc[x],mid+1,r,ql,qr);
    else return max(query(lc[x],l,mid,ql,mid),query(rc[x],mid+1,r,mid+1,qr));
}
void extend(int x,int l,int r)
{
    if(l>r)return;
    P t=query(x,1,n,l,r);
    q.push(node(t.first,x,l,r,t.second));
}

int main()
{
    //freopen("lx.in","r",stdin);
    n=getint(),m=getint();
    rt[0]=build(1,n);
    for(int i=1;i<=n;i++)
    {
        int x=getint();
        rt[i]=modify(rt[i-1],1,n,pre[x]+1,i,x);
        pre[x]=i;extend(rt[i],1,i);
    }
    while(m--)
    {
        tmp=q.top(),q.pop();
        extend(tmp.x,tmp.l,tmp.p-1);
        extend(tmp.x,tmp.p+1,tmp.r);
    }
    cout<<tmp.v<<'\n';
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值