【hihocoder 1046】K Seq(线段树)

本文介绍了如何使用可持久化线段树和动态开点算法来解决求字符串子串和第k大的问题。通过预处理每个元素的下次出现位置,并构建线段树维护以每个元素为首的子串长度值。在寻找第k大时,利用堆进行操作,每次取出最大值并更新原位置。在维护线段树最大值时,注意标记不下传的特性。
摘要由CSDN通过智能技术生成

传送门

I Think

    题意:求一个串子串和(定义为该串内不重复的元素和)第k大
    算法:可持久化线段树+动态开点
    思路:手动样例计算以第1、2、3……n个元素为串首的多个子串和,以S[i][]记以第i个元素为首的子串和们,预处理出每一个元素下一次出现的位置pos[i],会发现对于 j ∈ [i, pos − 1],S[i][j] 的元素值就等于 S[i − 1][j] 的元素值减去 val[i − 1],其他下标的元素的值不变。
    于是我们对每个元素建立线段树,维护以它为首的子串长度值。找第k大时先把每个位置的线段树的最大值放进一个堆里,每次取出一个最大值并将其原位置的值改为无穷小。(注意inf要开大)重复k-1次该过程即可。
    在维护线段树中最大值时Add的标记mk是没有下传的,因为每次Up加上了mk,递归回到根的过程中总会加上这个标记,这种维护最值而不是区间和的情况可以考虑标记不下传。

Code

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define f first
#define s second
using namespace std;

typedef long long LL;
const int sm = 1e5+10;
const int sn = 1e7;
const LL inf = 1e14+50;

template <typename T> T Max(T x,T y) { return x>y?x:y; } 
template <typename T> void read(T &x) {
    char ch=getchar();int f=1;x=0;
    while(ch>'9'||ch<'0') { if(ch=='-') f=-1;ch=getchar(); }
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); x*=f;
}

priority_queue<pair<LL,int > >que;
int n,k,tot,cnt;
int Ls[sn],Rs[sn];
LL S[sm],mx[sn],mk[sn],pos[sn];//pos[sn]
int A[sm],B[sm],T[sm],ex[sm],nxt[sm];

int Hash(int x) {
    return lower_bound(B+1,B+cnt+1,x)-B;
}
void Up(int rt) {
    mx[rt]=Max(mx[Ls[rt]],mx[Rs[rt]])+mk[rt];//mk[rt]
    pos[rt]=(mx[Ls[rt]]>mx[Rs[rt]])?pos[Ls[rt]]:pos[Rs[rt]];
}
void Build(int &rt,int l,int r) {
    rt=++tot;
    if(l==r) {
        mx[rt]=S[l],pos[rt]=l;return;
    }
    int m=(l+r)>>1;
    Build(Ls[rt],l,m);
    Build(Rs[rt],m+1,r);
    Up(rt);
}
void Modify(int &rt,int l,int r,int p) {//不要掉&
    int pt=rt;rt=++tot;
    mx[rt]=mx[pt],pos[rt]=pos[pt],mk[rt]=mk[pt],Ls[rt]=Ls[pt],Rs[rt]=Rs[pt];
    if(l==r) { mx[rt]=mk[rt]=-inf; return; }
    int m=(l+r)>>1;
    if(p<=m) Modify(Ls[rt],l,m,p);
    else Modify(Rs[rt],m+1,r,p);
    Up(rt);
}
void Add(int pre,int &rt,int l,int r,int a,int b,int val) {
    rt=++tot,mx[rt]=mx[pre],mk[rt]=mk[pre];
    pos[rt]=pos[pre],Ls[rt]=Ls[pre],Rs[rt]=Rs[pre];
    if(a<=l&&r<=b) {
        mk[rt]+=val,mx[rt]+=val; return ;
    }
    int m=(l+r)>>1;
    if(a<=m) Add(Ls[pre],Ls[rt],l,m,a,b,val);
    if(b> m) Add(Rs[pre],Rs[rt],m+1,r,a,b,val);
    Up(rt);
}
int main() {
    read(n),read(k);    
    for(int i=1;i<=n;++i) read(A[i]),B[i]=A[i],nxt[i]=n+1;
    sort(B+1,B+n+1);
    cnt=unique(B+1,B+n+1)-B;
    for(int i=1,hsh;i<=n;++i) {
        hsh=Hash(A[i]);
        if(!ex[hsh]) S[i]=S[i-1]+A[i],ex[hsh]=i;
        else nxt[ex[hsh]]=i,ex[hsh]=i,S[i]=S[i-1];
    }
    Build(T[1],1,n);
    que.push(make_pair(mx[T[1]],T[1]));
    for(int i=2;i<=n;++i) {
        Add(T[i-1],T[i],1,n,i,nxt[i-1]-1,-1*A[i-1]);
        Modify(T[i],1,n,i-1);
        que.push(make_pair(mx[T[i]],T[i]));
    }
    for(int i=1,p;i<k;++i) {
        p=que.top().s,que.pop();
        Modify(p,1,n,pos[p]);
        que.push(make_pair(mx[p],p));
    }
    printf("%lld\n",que.top().f);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值