A. 【五一省选集训day5】超简单题
A. 【五一省选集训day5】超简单题
给定一个字符串 \(S\),有 \(q\) 次询问,每次问 \(S\) 第 \(k\) 大的非空子序列(去重后)是什么。
不去重的非空子序列个数是 \(2^n-1\),但是本题要求去重。直接说结论。设 \(f_i\) 表示以 \(S_i\) 开头的不同的子序列个数,设 \(ne_{i,ch}\) 表示 \(i\) 后面第一个字符 \(ch\) 的位置。则 \(f_i=1+\sum_{ch=a}^{z} f_{ne_{i+1,ch}}\)。因为 \(k\le 10^{18}\),所以超过 \(10^{18}\) 的 \(f\) 赋值为 \(10^{18}\) 就好了。
建立子序列自动机。连一条标号 \(ch\) 的边 \(i\to ne_{i+1,ch}\)。每个节点预处理 \(f\) 数组。每次询问暴力跳,时间复杂度是 \(O(nq)\) 的。
然后有一个经典?吗的套路。\(Dag\) 重链剖分。首先我们可以预处理,跳到第一个 \(\le 10^{18}\) 的位置。然后对于剩下的节点,钦定 \(f_{ne_{i+1,ch}}\) 最大的点 \(ne_{i+1,ch}\) 为重儿子。剖分出若干条重链。跳到一个点上,相当于缩小了排名的上界和下界。对于重链,我们倍增地预处理跳到每层的贡献,询问时在重链上倍增地跳,一直跳到链尾或者排名出界。应该也可以预处理重链上跳到每一个点的贡献,然后询问时二分。如果跳到最后面或者出界,就暴力枚举跳一条轻链。因为轻链满足点权 \(\le\) 父亲的一半,所以只会跳 \(\log\) 次。
没啦,一共跳 \(\log\) 条重链,每条重链跳 \(\log\) 下,时间复杂度是 \(O(q\log^2 n)\) 的。
code
真难写
#include<bits/stdc++.h>
//#define LOCAL
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
const int N=3e5+7;
const ll inf=2e18;
int n,q;
int lst[26],ne[N][26];
char c[N];
ll f[N],sum[N][26],pre[N],mx;
int son[N][20];
int ss[N];
ll S[N][20];
ll k;
int p;
int l[N],r[N],to[N],top,dep[N];
int ans[N],as;
int main() {
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("my.out","w",stdout);
#endif
sf("%s",c+1);
n=strlen(c+1);
rep(i,0,25) lst[i]=n+1;//lst 字符 i 最近出现的位置
rep(i,0,19) son[n+1][i]=n+1;//son 转移的儿子,倍增数组
per(i,n,0) {
memcpy(ne[i],lst,sizeof (ne[i]) ) ;//ne 转移的儿子
rep(j,0,25) {
sum[i][j]=min(inf,(j?sum[i][j-1]:0)+f[lst[j]]);//sum 儿子的前缀和
}
f[i]=1+sum[i][25];//f 点权,即以这个点开头的不同子序列个数
son[i][0]=lst[0];//直接儿子
mx=f[lst[0]];//重儿子点权
rep(j,1,25) {//找重儿子
if(sum[i][j]-sum[i][j-1]>=mx) {
mx=sum[i][j]-sum[i][j-1];//更新重儿子点权
son[i][0]=lst[j],S[i][0]=sum[i][j-1];//更新重链信息
ss[i]=j;//重链字符
}
}
++S[i][0];//个数加上所有儿子都不选(只选 i)的情况
if(i) lst[c[i]-'a']=i;
}
rep(i,1,18) {//倍增预处理
rep(j,0,n) {
son[j][i]=son[son[j][i-1]][i-1];
S[j][i]=S[j][i-1]+S[son[j][i-1]][i-1];
}
}
sf("%d",&q);
while(q--) {
sf("%lld%d",&k,&p);
if(k>=f[0]) {pf("-1\n");continue;}
l[top=1]=0,as=0;
while(1) {
int u=l[top];//目前重链起点
dep[top]=0;//目前重链走的长度
per(j,18,0) {//倍增跳重链
if(S[u][j]<=k&&S[u][j]+f[son[u][j]]>k) {
k-=S[u][j];u=son[u][j],dep[top]+=1<<j;
}
}
r[top]=u;//目前重链终点
if(!k) break;//找到了
--k,++top;
rep(j,0,25) {//跳轻边
if(f[ne[u][j]]>k) {u=ne[u][j],to[top-1]=j;break;}
else k-=f[ne[u][j]];
}
l[top]=u;
}
//as 已经计入了几位
per(i,top,1) {//枚举每条重链
int u=l[i],s=max(0,dep[i]-(p-as));//s 有几位是不要输出的
if(s) per(j,18,0) if((s>>j)&1) u=son[u][j];//跳到第一个要输出的地方
int tmp=as;
while(u!=r[i]) ans[++as] = ss[u],u=son[u][0];//一个一个跳,计入答案
reverse(ans+tmp+1,ans+as+1);
if(as==p) break;
if(i>1) ans[++as] = to[i-1];//跳轻边
}
per(i,as,1) putchar(ans[i]+'a');putchar('\n');
}
}
本文来自博客园,作者:liyixin,转载请注明原文链接:https://www.cnblogs.com/liyixin0514/p/18429817