AT_s8pc_5_h Percepts of Atcoder题解

AtCoder

参考 这篇题解 的时候感觉时间复杂度可以卡掉,所以我补充一下 DAG 链剖分这个做法怎么做。

暴力需要找到字典序排在第 \(k\) 的子序列,考虑建立序列自动机,也就是从起始点开始向下走,到任意点的每条路径都对应一个原字符串的子序列。

为了便于求出后面的 \(p\) 个字符,我们可以想到先用某些方法压缩一下路径,再把需要使用的路径部分展开。

这时候可以回想一下在树上问题时我们采用重链剖分压缩路径,那么在这个问题中,我们也可以采用类似的方法。

得出序列自动机后,我们可以轻易得到这个点为起点有多少走法。

然后我们把一个点到其儿子中走法最多的点之间的边连接为重边。

由于每个点的走法同样是通过求和得到的,与树的子树大小类似,再加上因为 \(k\) 的范围限制最大不需要超过 \(10^{18}\) 的条件。我们将每个节点的走法和 \(10^{18}\) 取较小值,若儿子中存在大小为 \(10^{18}\) 的,那么重儿子就设置为最前面的 \(10^{18}\),因为这个值最大,后面的点也就没有机会了,不影响,所以可以得到走的轻边数量与树链剖分相似,是 \(\log siz\),而其中的 \(siz\le 10^{18}\)

考虑重链如何压缩,我们考虑倍增,预处理出每一步到达的位置是否包含要走的子序列,可以直接预处理,需要注意边界条件。

剩下的问题在于解除压缩,参考的题解中会把用到的重链直接全部展开,这其实是有问题的,如果有多次询问都展开了一个长度接近原串的重链,时间复杂度就会退回 \(O(Q|S|)\) 了。

因此,在处理到最后一个重链时,我们先倍增到达需要加入的点,然后再展开后面德内容进行处理,这样只会给每次询问加上一个 \(O(\log |S|)\) 的时间复杂度,总的时间复杂度不变。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
char s[300005];
int n,Q,son[300005][26],hson[300005],cnt[300005],fa[300005][20],st[300005][20],L[300005][20],R[300005][20];
string query(int k,int p){
	if(k>cnt[0])return "-1";
	vector<pair<int,pair<int,int>>> ve;
	int w=0;
	while(k){
		for(int i=19;i>=0;i--){
			if(k>=L[w][i]&&k<=R[w][i]){
				ve.push_back({1,{w,i}});
				k-=st[w][i];
				w=fa[w][i];
			}
		}
		if(w)k--;
		if(!k)break;
		for(int i=0;i<26;i++){
			if(cnt[son[w][i]]>=k){
				ve.push_back({0,{w,i}});
				w=son[w][i];
				break;
			}
			k-=cnt[son[w][i]];
		}
	}
	reverse(ve.begin(),ve.end());
	string ans="";bool fl=0;
	for(auto tmp:ve){
		if(tmp.first){
			int x=tmp.second.first;
			string t="";
			if((1<<tmp.second.second)+ans.size()<=p){
				for(int j=1;j<=(1<<tmp.second.second)&&x!=n+1;j++)t+=(char)(hson[x]+'a'),x=fa[x][0];
			}
			else {
				int d=(1<<tmp.second.second)-(p-ans.size());
				for(int j=19;j>=0;j--)if(d>>j&1)x=fa[x][j];
				for(int j=1;j<=p-ans.size()&&x!=n+1;j++)t+=(char)(hson[x]+'a'),x=fa[x][0];
			}
			reverse(t.begin(),t.end());
			ans+=t;
			if(x==n+1)break;
		}
		else {
			if(tmp.second.first==n+1)break;
			ans+=(char)(tmp.second.second+'a');
		}
		if(ans.size()==p)break;
	}
	reverse(ans.begin(),ans.end());
	return ans;
}
signed main(){
	scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=0;i<26;i++)son[n][i]=n+1;
	cnt[n]=1;
	for(int i=n-1;i>=0;i--){
		for(int j=0;j<26;j++)son[i][j]=son[i+1][j];
		son[i][s[i+1]-'a']=i+1;
		if(i)cnt[i]=1;
		int ma=-inf;
		for(int j=0;j<26;j++){
			if(!son[i][j])continue;
			if(ma<cnt[son[i][j]]){
				hson[i]=j;
				fa[i][0]=son[i][j];
				st[i][0]=cnt[i];
				L[i][0]=cnt[i]+1;
				R[i][0]=min(cnt[i]+cnt[son[i][j]],inf);
				ma=cnt[son[i][j]];
			}
			cnt[i]=min(cnt[i]+cnt[son[i][j]],inf);
		}
	}
	fa[n+1][0]=n+1,L[n+1][0]=1,R[n+1][0]=0;
	for(int i=1;i<20;i++){
		for(int j=0;j<=n+1;j++){
			int u=fa[j][i-1];
			fa[j][i]=fa[u][i-1];
			st[j][i]=min(st[j][i-1]+st[u][i-1],inf);
			L[j][i]=max(st[j][i-1]+L[u][i-1],L[j][i-1]);
			R[j][i]=min(st[j][i-1]+R[u][i-1],R[j][i-1]);
		}
	}
	cin>>Q;
	while(Q--){
		int k,p;cin>>k>>p;
		cout<<query(k,p)<<endl;
	}
	return 0;
}

posted @ 2026-07-07 21:00  huhangqi  阅读(5)  评论(0)    收藏  举报