DTOJ#5115 前缀

本文介绍了一种算法问题,如何在无限循环的字符串sss中找到最短的前缀,使得其包含给定字符串ttt,其中ttt包含特殊字符*作为通配符。涉及暴力模拟和字符串处理技巧,解决实际长度可达到10的10万次方的输入问题。
摘要由CSDN通过智能技术生成

题面

牛牛有一个 s s s 串, s s s 串仅由 26 26 26 个小写英文字母组成,他现在将 s s s 串进行了无限次的复制扩展成了一个无线循环串。

例如一开始 s s sabc,那么牛牛就会将其变为 abcabcabc...

若某个字符串保留其原本字符出现的顺序,并且按照顺序取出若干个字符。

可以不连续,可以不取。

我们称取出的这若干个字符连成的字符串为一个子序列。

若连续取出某个字符串的前 k k k 个字符,组成一个子串,我们称该字符串为原串长度为 k k k 的前缀。

对于一个字符串 t t t,若某字符串的至少一个子序列为 t t t。则称它是一个 t t t 序列串

牛牛想要知道对于给定的 t t t,他想要知道 s s s 的一个最短前缀满足它是一个含 t t t 序列串,它的长度有多长?

由于答案可能非常大,所以他要求你输出答案对 998244353 998244353 998244353 取余数后的结果即可。

特别的,如果 s s s 串不存在任何一个前缀满足他是一个含 t t t 序列串,请输出 − 1 -1 1 表示无解。

t t t 串中除了 26 26 26 个英文字母以外还会出现 *,表示一个通配符。

统配符可以视为任意字母。

例如循环 s s s 串为 abcabcabcabc... t t t 串为 a∗ca 时,最短含 t t t 序列前缀长 4 4 4

而当 t t t 串为 a∗∗ca 时,最短含 t t t 序列前缀长 7 7 7

除此之外,牛牛输入的 t t t 串还可能非常非常长,最长可以达到 1 0 1 0 5 10^{10^5} 10105 这么长。

所以他想了一种压缩方法,来快速读入 t t t 串。

具体来说,它输入的 t t t 串中除了 * 26 26 26 个小写英文字母以外,还会跟有一些正整数。

在读入字符串时,这些数字表示它前面字母或者 * 重复的次数。

例如 a5bc*3,表示 aaaaabc***。输入的正整数不含前导 0 0 0

数据范围

对于前 10 % 10 \% 10% 的测试数据保证 1 ≤ n ≤ 100 , 1 ≤ ∣ s ∣ , ∣ t ∣ ≤ 10 1 \leq n \leq 100,1 \leq \lvert s \rvert, \lvert t \rvert \leq 10 1n100,1s,t10 t t t 串中不包含数字以及 *

对于前 20 % 20 \% 20% 的测试数据保证 1 ≤ n ≤ 100 , 1 ≤ ∣ s ∣ , ∣ t ∣ ≤ 10 1 \leq n \leq 100,1 \leq \lvert s \rvert, \lvert t \rvert \leq 10 1n100,1s,t10 t t t 串中不包含数字。

对于前 30 % 30 \% 30% 的测试数据保证 1 ≤ n ≤ 1000 , 1 ≤ ∣ s ∣ , ∣ t ∣ ≤ 1000 1 \leq n \leq 1000,1 \leq \lvert s \rvert, \lvert t \rvert \leq 1000 1n1000,1s,t1000 t t t 串中不包含数字。

对于前 60 % 60 \% 60% 的测试数据保证 1 ≤ n ≤ 1 0 5 , 1 ≤ ∣ s ∣ ≤ 1 0 4 , 1 ≤ ∣ t ∣ ≤ 1 0 5 1 \leq n \leq 10^5, 1 \leq \lvert s \rvert \leq 10^4, 1 \leq \lvert t \rvert \leq 10^5 1n105,1s104,1t105 t t t 串中的数字值域范围在 [ 1 , 1 0 9 ] [1, 10^9] [1,109] 内。

对于前 100 % 100 \% 100% 的测试数据保证 1 ≤ n ≤ 1 0 5 , 1 ≤ ∣ s ∣ ≤ 1 0 4 , 1 ≤ ∣ t ∣ ≤ 1 0 5 , ∑ ∣ t ∣ ≤ 1 0 6 1 \leq n \leq 10^5,1 \leq \lvert s \rvert \leq 10^4,1 \leq \lvert t \rvert \leq 10^5, \sum \lvert t \rvert \leq 10^6 1n105,1s104,1t105,t106 t t t 串中的数字值域范围在 1 0 1 0 5 10^{10^5} 10105 内。

注意, ∣ t ∣ \lvert t\rvert t 仅表示输入时的压缩串的长度,不代表解压缩后的长度,解压后 t t t 串的长度最长可以达到 1 0 1 0 5 10^{10^5} 10105

题解

这是一道暴力模拟(ex)题:
对于 t t t 串中的每一种字符,我们都必须先把 s s s 串中的该字符匹配完才能匹配下一个字符,所以我们就求出该字符在 s s s 串中的数量,以及位置,然后把该字符需要匹配的数量分为能匹配完整个 s s s 环的部分,和不能匹配完的部分。这个数量边读入边取模即可。
然后就是亿堆ex的细节

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e6+10;
const ll P=998244353;
ll n,m,len,k,pos[35][N],cnt[35];
ll ans,s1,s2;
char c[N],t[N];
void prework(){
	for(int i=1;i<=len;++i)pos[c[i]-'a'+1][++cnt[c[i]-'a'+1]]=i;
}
void solve(int ck,int s){
	k=pos[ck][s];
	ans=(ans+k)%P;
}
int erfen(int ck,int k){
	int l=1,r=cnt[ck]+1;
	while(l<r){
		int mid=(l+r)>>1;
		if(pos[ck][mid]>k)r=mid;
		else l=mid+1;
	}
	return l;
}
void work(){
	int len_t=strlen(t),ck;k=0;
	//cout<<"=----"<<len<<endl;
	ll s=0;
	char ck311='0';
	for(int i=1;i<=len_t;++i){
		//cout<<"FFF "<<i<<" "<<ck<<endl;
		if(t[i]>='a'&&t[i]<='z'){
		if((!cnt[t[i]-'a'+1])){
		ans=-1;return;}}
		
		if(t[i]>='0'&&t[i]<='9'){
			s2=s2*10+t[i]-'0';
			s1=(s1*10+s2/cnt[ck])%P;
			s2%=cnt[ck];
		}
		else{
			if(i>1){
			if(t[i-1]<'0'||t[i-1]>'9')s2=1;
			//cout<<i<<" "<<s1<<" "<<s2<<endl;
			int l=erfen(ck,k);
			if(l<=cnt[ck])ans+=pos[ck][l]-k,k=pos[ck][l];
			else ans+=len-k,k=0,ans+=pos[ck][erfen(ck,k)],k=pos[ck][erfen(ck,k)];
			if(s2)s2--;
			else if(s1)s1--,s2=cnt[ck]-1;
			if(s1){
			if(s2)ans=(ans+s1*len%P)%P;
			else ans=(ans+(s1-1)*len%P)%P,s2=cnt[ck];
			}
			//cout<<"FAQ "<<ans<<endl;
			if(s2){
			l=erfen(ck,k);
			if(l+s2-1>cnt[ck]){
				ans=(ans+P+P+len-k)%P;
				s2-=cnt[ck]-l+1;
				k=0;
				solve(ck,s2);
			}
			else{
				ans=(ans+P+pos[ck][l+s2-1]-k)%P;
				k=pos[ck][l+s2-1];
			}
			}
		}
			s1=0;s2=0;
			ck311=t[i];
			if(ck311=='*')ck=28;
			else ck=t[i]-'a'+1;
		}
		}
}
int main(){
//	freopen("ex.in","r",stdin);
//	freopen("ex.out","w",stdout);
	scanf("%s",c);
	len=strlen(c);
	cnt[28]=len;
	for(int i=len;i>0;--i)c[i]=c[i-1];
	for(int i=1;i<=len;++i)pos[28][i]=i;
	//cout<<"FFFFAQQQ "<<len<<endl;
	prework();
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		t[0]='0';ans=0;k=0;
		scanf("%s",t+1);
		work();
		printf("%lld\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值