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');
	}
}
posted @ 2024-09-24 22:10  liyixin  阅读(6)  评论(0编辑  收藏  举报