2023NOIP A层联测10 子序列

文章讨论了如何解决一个关于字符串的好子序列查询问题,通过预处理字符出现频率和段落信息,利用动态规划和字符组合分析,以O(nv+mv^2)的时间复杂度求解最长好子序列及其表示的两个字符。

题目大意

给定一个长度为nnn的仅由小写字母构成的字符串SSS。我们定义一个字符串是好的,当且仅当它可以用两个不同的字母xxxyyy表示成xyxyxyx…xyxyxyx\dotsxyxyxyx的形式。比如字符串ababababababtottottotaaa是好的,但字符串abcabcabcaaaaaa不是好的。

现在有qqq组询问,每次给定1≤l≤r≤n1\leq l\leq r\leq n1lrn,求SSS的子串S[l⋯r]S[l\cdots r]S[lr]的最长的一个好的子序列的长度是多少,以及它可以被哪两个字符xxxyyy来表示。如果有多个最长的串,则输出字典序最小的一个串的xxxyyy

1≤n≤1.5×106,1≤m≤1051\leq n\leq 1.5\times 10^6,1\leq m\leq 10^51n1.5×106,1m105


题解

对每个字符iii,考虑在只保留它时序列被分为若干段(???i??i???i??),预处理zti,j,kzt_{i,j,k}zti,j,k表示只保留字符iii时字符jjj能否在第kkk段出现,并用fi,j,kf_{i,j,k}fi,j,k记录zti,j,kzt_{i,j,k}zti,j,k的前缀和。预处理lti,jlt_{i,j}lti,jrti,jrt_{i,j}rti,j,分别表示在iii之前的第一个字符jjj和在iii之后的第一个字符jjj。因为所有iii的数量的和为nnn,所以这部分的时间复杂度为O(nv)O(nv)O(nv),其中vvv为字符的个数,也就是小写字母的个数,即262626

对于一组询问l,rl,rl,r,枚举一种字符iii,用ltltltrtrtrt来确定这段区间中ccc第一次出现的位置vlvlvl和最后一次出现的位置vrvrvr,求出这两个位置的段标号blblblbrbrbr,再枚举第二种字符jjj,那么整段的贡献为fi,j,br−fi,j,bl−1f_{i,j,br}-f_{i,j,bl-1}fi,j,brfi,j,bl1。对于两边的散块,用ltltltrtrtrt来求贡献即可。这部分的时间复杂度为O(mv2)O(mv^2)O(mv2)

总时间复杂度为O(nv+mv2)O(nv+mv^2)O(nv+mv2)

code

#include<bits/stdc++.h>
using namespace std;
const int N=1500000,M=100000,K=26;
int n,m,l,r,ans,zt[N+5],id[N+5],lt[K][N+5],rt[K][N+5];
char v1,v2,s[N+5];
vector<int>w[K],f[K][K];
int main()
{
//	freopen("seq.in","r",stdin);
//	freopen("seq.out","w",stdout);
	scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;i++){
		id[i]=w[s[i]-'a'].size();
		w[s[i]-'a'].push_back(i);
	}
	for(int i=0;i<26;i++){
		for(int k=0;k+1<w[i].size();k++){
			for(int j=w[i][k]+1;j<w[i][k+1];j++){
				zt[s[j]-'a']=1;
			}
			for(int j=0;j<26;j++){
				f[i][j].push_back(zt[j]);zt[j]=0;
				if(k) f[i][j][k]=f[i][j][k]+f[i][j][k-1];
			}
		}
	}
	for(int i=0;i<26;i++){
		lt[i][0]=0;
		for(int j=1;j<=n;j++){
			lt[i][j]=lt[i][j-1];
			if(s[j]-'a'==i) lt[i][j]=j;
		}
		rt[i][n+1]=n+1;
		for(int j=n;j>=1;j--){
			rt[i][j]=rt[i][j+1];
			if(s[j]-'a'==i) rt[i][j]=j;
		}
	}
	scanf("%d",&m);
	while(m--){
		scanf("%d%d",&l,&r);
		ans=0;
		for(int i=0;i<26;i++){
			int vl=rt[i][l],vr=lt[i][r];
			if(vl>vr) continue;
			if(vl>=n+1||vr<=0) continue;
			int bl=id[vl],br=id[vr];
			for(int j=0;j<26;j++){
				if(i==j) continue;
				if(rt[i][l]>rt[j][l]) continue;
				int tmp=0;
				if(br) tmp+=f[i][j][br-1];
				if(bl) tmp-=f[i][j][bl-1];
				tmp=tmp*2+1;
				if(rt[j][vr]<=r||lt[j][vl]>=l) ++tmp;
				if(tmp>ans){
					ans=tmp;
					v1='a'+i;v2='a'+j;
				}
			}
		}
		printf("%d %c%c\n",ans,v1,v2);
	}
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值