jzoj 5573. 子序列

Description
 
Input
Output
 
Sample Input
输入1:
2 3 1 5
1 2

输入2:
3 4 2 3
1 3 1

输入3:
5 6 23 1000
1 2 4 2 3
Sample Output
输出1:
1
3
2

输出2:
1
1
0
2

输出3:
1
25
25
577
274
578
 Sol :   

    经典的用优先队列求前?大的题。 我们从小到大一个一个取出子序列。然后考虑如何进行拓展。 最开始我们肯定选择最小的数字,选完这个数字之后,我们有两种拓展方式,一种 是不选择这个数字,改为选择比它大的最小的数字(数字相同的位置越小的视为更小), 并且这个数字要在当前子序列的上一个元素之后,可以用主席树快速查找。第二种是强 制选择这个数字,并选择这个数字之后的最小的数字。 把所有可以拓展的状态都丢入堆中,现在最关键的问题是快速比较两个子序列的字 典序。肯定不能直接存整个子序列,但是注意到如果一个子序列出现了,那么它的所有 前缀也一定出现了,所以可以用Trie维护子序列,空间总复杂度是线性的。有了Trie树, 用lca就可以快速判断两个子序列的字典序了。

 Code:
#include<bits/stdc++.h>
using namespace std;
#define sight(x) ('0'<=x&&x<='9')
#define Mid (l+r>>1)
#define M 2200001
#define Siz 100001
#define LL long long
vector<int> V[Siz+7];
int siz[M],ls[M],rs[M],tot,Kb,pos,pos2,rt[Siz+7],n,a[Siz+7],P[Siz+7],Ha[Siz+7],Pos[M];
LL p,seed;
template <class T>
inline void read(T &x){
    static char c;
    for (c=getchar();!sight(c);c=getchar());
    for (x=0;sight(c);c=getchar())x=x*10+c-48;
}
void write(int x){if (x<10) {putchar('0'+x); return;} write(x/10); putchar('0'+x%10);}
inline void writeln(int x){ if (x<0) putchar('-'),x*=-1; write(x); putchar('\n'); }
inline void writel(int x){ if (x<0) putchar('-'),x*=-1; write(x); putchar(' '); }
void insert(int last,int &now,int l,int r,int pos,int pos2) {
    now=++tot;
    siz[now]=siz[last]+1; Pos[now]=pos2;
    if (l==r) return;
    if (pos<=Mid) rs[now]=rs[last],insert(ls[last],ls[now],l,Mid,pos,pos2);
    else ls[now]=ls[last],insert(rs[last],rs[now],Mid+1,r,pos,pos2);
}
void find(int last,int now,int l,int r,int k) {
    if (l==r) {
        pos=Pos[now];
        return;
    }
    if (siz[ls[now]]-siz[ls[last]]>=k) find(ls[last],ls[now],l,Mid,k);
    else find(rs[last],rs[now],Mid+1,r,k-siz[ls[now]]+siz[ls[last]]);
}
int Find(int l,int r,int p){
    return find(rt[n+1],rt[l+1],1,Siz,p),pos;
}
int tt,nxt[Siz+7],last[Siz+7];
void Sol(int l,int r) {
    int o=P[l]; 
    for (int i=1;i<=n-o;i++) {
        int x=Find(o,n,i),la=tt,ww=0;
        for (int j=x;j;j=nxt[j]){
            ww++;
            for (int k=l;k<=r;k++) {
                if (j<=P[k]) continue;
                P[++tt]=j; Ha[tt]=(1ll*Ha[k]*seed+a[j])%p;
                if (tt==Kb) {
                    for (int ch=1;ch<=Kb;ch++) writeln(Ha[ch]);
                    exit(0);
                }
            }
        }
        Sol(la+1,tt),i+=ww-1;
    }
}
signed main () {
    freopen("sequence.in","r",stdin);
    freopen("sequence.out","w",stdout);
    read(n); read(Kb); read(seed); read(p);
    for (int i=1;i<=n;i++) {
      read(a[i]);  V[a[i]].push_back(i);  
      nxt[last[a[i]]]=i; last[a[i]]=i;
    } 
    for (int i=n;i;i--) 
     insert(rt[i+1],rt[i],1,Siz,a[i],i);
    Sol(0,0);
    return 0;
}

 

 

转载于:https://www.cnblogs.com/rrsb/p/9878791.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值