Description
Solution
考虑现在已经找到字典序第
i
小的子序列,设它为
1.
2.
如果对于这些序列设置一个比较函数,那么就是经典问题:将字典序最小的序列从堆中提出来,加入它的两个扩展到堆中。
我们发现序列长度较长不能保存,但其实子序列之间有很多前缀关系,我们可以使用一棵trie,通过trie上的节点来表示这些子序列。至于两个子序列的比较,可以通过它们的
lca
往下一个儿子的大小关系来比较。
堆或优先队列复杂度
O(log2n)
,子序列比较
O(log2n)
,所以总复杂度是
O(klog22n)
的。
但这个方法常数较大,且不易实现。
于是我们不机械的把所有可能的子序列都丢进堆里,靠比较来得出序列,我们可以直接构造。
设答案(即最终序列集)为
[1,k]
,对于一段哈希值相同的序列集区间
[l,r]
,它一定是由同样
[l′,r′](r′<l)
中的序列(区间中哈希值相同)直接构造出来的。
考虑对于一段序列集
[l,r]
,如何构造字典序最小且刚好能接在已有的序列集后。
设
posx
表示
x
这个序列的结尾在原序列的位置。
枚举
Code
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i,j,k) for(int i=j;i<=k;++i)
#define fd(i,j,k) for(int i=j;i>=k;--i)
#define ll long long
using namespace std;
const int N=1e5+10;
int a[N],tot=0;
struct tree{
int l,r,x,p;
}tr[N*20];
int rt[N],nx[N],mx=0;
int wz[N];
int n,K,seed,mo;
void insert(int &v,int l,int r,int x){
tr[++tot]=tr[v],v=tot;
if(l==r) {tr[v].p=x,tr[v].x++;return;}
int mid=(l+r)>>1;
a[x]<=mid?insert(tr[v].l,l,mid,x):insert(tr[v].r,mid+1,r,x);
tr[v].x=tr[tr[v].l].x+tr[tr[v].r].x;
}
int find(int v,int l,int r,int x){
if(x>tr[v].x) return 0;
if(l==r) return tr[v].p;
int mid=(l+r)>>1;
return tr[tr[v].l].x<x?find(tr[v].r,mid+1,r,x-tr[tr[v].l].x):find(tr[v].l,l,mid,x);
}
struct node{
int p;
ll hs;
}b[N];
int tt=0;
void dfs(int l,int r){
int o=b[l].p;
fo(i,1,tr[rt[o+1]].x){
int p=find(rt[o+1],1,mx,i),nw=tt,ww=0;
for(int j=p;j;j=nx[j]){
ww++;
fo(k,l,r)
if(j>b[k].p){
b[++tt].p=j,b[tt].hs=(b[k].hs*seed+a[j])%mo;
if(tt==K){
fo(ch,1,K) printf("%lld\n",b[ch].hs);
exit(0);
}
}
}
dfs(nw+1,tt),i+=ww-1;
}
}
int main()
{
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
scanf("%d %d %d %d",&n,&K,&seed,&mo);
fo(i,1,n){
scanf("%d",&a[i]),mx=max(mx,a[i]);
nx[wz[a[i]]]=i,wz[a[i]]=i;
}
nx[0]=0;
fd(i,n,1)
rt[i]=rt[i+1],insert(rt[i],1,mx,i);
dfs(0,0);
}