JZOJ 5573 子序列

子序列

Description

给出一个长度为 n 的序列a,求 a 的字典序前k小的非空子序列的哈希值。
一个序列 b1...m 的哈希值为 mi=1biseedmi mod p

Data Constraint

n <=105 , 1 <=seed,p<= 106 , 1 <=ai<= 105 , 1 <=k<= min(105,2n1)

Solution

这种题目的做法应该非常经典了。
用一个优先队列保存有可能成为当前最优解的子序列。

我们每次选择字典序最小的子序列,选完这个子序列之后,我们有两种拓展方式。
一种是不选择子序列最后的那个数字,改为选择在倒数第二个数后比它大的最小的数字(数字相同时位置越前的越小),这个可以用主席树快速查找实现。
第二种是保留选择最后的那个数字,并选择这个数字之后的最小的数字。

把所有可以拓展的状态都丢入优先队列中,易证当前的最优解必为优先队列中的一个。
现在最关键的问题是如何快速比较两个子序列的字典序,肯定不能直接储存整个子序列,但是注意到如果一个子序列出现了,那么它的所有前缀也一定出现过了,所以可以用 Trie 维护子序列,用 map 储存 Trie 树上的边集,空间总复杂度是线性的。
有了 Trie 树,用 lca 就可以快速判断两个子序列的字典序了。

时间复杂度 O(k log2 n)

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#pragma GCC optimize(2)

#define fo(i,j,l) for(int i=j;i<=l;++i)
#define fd(i,j,l) for(int i=j;i>=l;--i)

using namespace std;
typedef long long ll;
const ll N=61e4,M=N<<2;

int n,kk,w[N],d[N],c[N],u,v,oo,pos[N],ok[N];
int dep[N],r[N];
int f[N][20],last[N],back[N];
int root[M],g[M],ls[M],rs[M],cb[N];
ll dq[N],seed,p;
map<int,int> z[N];

inline int min(int a,int b)
{return a<b?a:b;}
inline int max(int a,int b)
{return a>b?a:b;}

struct Comp{
    bool operator ()(int a,int b)
    {
        int aa=0,bb=0,w1=r[a],w2=r[b];
        if(dep[w1]<dep[w2]){
            bb=1; 
            for(int l=19;l>=0;--l)
            if(dep[f[w2][l]]>=dep[w1])w2=f[w2][l];
        }
        if(dep[w1]>dep[w2]){
            aa=1;
            for(int l=19;l>=0;--l)
            if(dep[f[w1][l]]>=dep[w2])w1=f[w1][l];
        }
        for(int l=19;l>=0;--l)if(f[w1][l]!=f[w2][l])w1=f[w1][l],w2=f[w2][l];
        return w1!=w2?cb[w1]>cb[w2]:aa!=bb?aa>bb:a<b;
    }
};

priority_queue<int,vector<int>,Comp> s;

int cor(int o,int l,int r,int po)
{
    if(l==r){g[++v]=g[o]+1; return v;}
    int mid=l+r>>1;
    int k=++v;
    if(po<=mid)rs[k]=rs[o],ls[k]=cor(ls[o],l,mid,po);
    else ls[k]=ls[o],rs[k]=cor(rs[o],mid+1,r,po);
    g[k]=g[ls[k]]+g[rs[k]];
    return k;
}

int req(int zb,int yb,int l,int r)
{
    if(l==r)return l;
    int mid=l+r>>1;
    if(g[ls[yb]]-g[ls[zb]])return req(ls[zb],ls[yb],l,mid);
    else return req(rs[zb],rs[yb],mid+1,r); 
}

int req1(int zb,int yb,int l,int r,int dx)
{
    if(l==r)return g[yb]-g[zb];
    int mid=l+r>>1;
    if(dx<=mid)return req1(ls[zb],ls[yb],l,mid,dx);
    else return g[ls[yb]]-g[ls[zb]]+req1(rs[zb],rs[yb],mid+1,r,dx);
}

int req2(int zb,int yb,int l,int r,int dx)
{
    if(l==r)return l;
    int mid=l+r>>1;
    if(g[ls[yb]]-g[ls[zb]]>=dx)return req2(ls[zb],ls[yb],l,mid,dx);
    else return req2(rs[zb],rs[yb],mid+1,r,dx-g[ls[yb]]+g[ls[zb]]);
}

void llb(int a,int b,int op)
{
    if(z[a][op]==0){
        ++oo; z[a][op]=oo; r[b]=oo; 
        int u=oo; cb[u]=op;
        f[u][0]=a; dep[u]=dep[a]+1;
        for(int l=0;f[f[u][l]][l];++l)f[u][l+1]=f[f[u][l]][l];
    }else r[b]=z[a][op];
}

inline int read()
{
    int o=0; char ch=' ';
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';ch=getchar())o=o*10+ch-48;
    return o;
}

int main()
{
    cin>>n>>kk>>seed>>p; seed%=p; 
    int zd=0;
    fo(i,1,n)d[i]=read(),++c[d[i]],zd=max(zd,d[i]);
    fo(i,1,zd)c[i]=c[i-1]+c[i];
    fo(i,1,n){
        int k=d[i];
        d[i]=++c[d[i]-1];
        back[d[i]]=k; pos[d[i]]=i;
    }
    fo(i,1,n)root[i]=cor(root[i-1],1,n,d[i]);
    s.push(1); last[1]=0; int v=1;
    fo(i,1,kk+1){
        int k=s.top();
        if(i!=1)printf("%lld\n",(dq[k]%p+p)%p);
        s.pop();
        if(w[k]<n){
            int y=req(root[w[k]],root[n],1,n);
            ++v; last[v]=w[k];
            dq[v]=(dq[k]*seed+back[y])%p;
            w[v]=pos[y];
            llb(r[k],v,back[y]); 
            s.push(v);
        }
        if(w[k]!=0){
            int rank=req1(root[last[k]],root[n],1,n,d[w[k]]);
            if(rank!=n-last[k]){
                int y=req2(root[last[k]],root[n],1,n,rank+1);
                ++v; last[v]=last[k];
                w[v]=pos[y];
                dq[v]=(dq[k]-back[d[w[k]]]+p+back[y])%p;
                llb(f[r[k]][0],v,back[y]);
                s.push(v); 
            }
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值