2020牛客NOIP赛前集训营-提高组(第二场)C 前缀

题目

传送门

题目描述

牛牛有一个 s s s串, s s s串仅由 26 26 26个小写英文字母组成,他现在将 s s s串进行了无限次的复制扩展成了一个无限循环串。
例如一开始 s = “ a b c ” s=“abc” s=abc,那么牛牛就会将其变为 “ a b c a b c a b c . . . ” “abcabcabc...” abcabcabc...
若某个字符串保留其原本字符出现的顺序,并且按照顺序取出若干个字符。可以不连续,可以不取。
我们称取出的这若干个字符连成的字符串为一个子序列。
若连续取出某个字符串的前 k k k个字符,组成一个子串,我们称该字符串为原串长度为 k k k的前缀。
对于一个字符串 t t t,若某字符串的至少一个子序列为 t t t。则称它是一个 “ “ t t t序列串 ” ”
牛牛想要知道对于给定的t,他想要知道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串为 “ a b c a b c a b c a b c . . . " “abcabcabcabc..." abcabcabcabc..." t t t串为 “ a ∗ c a ” “a*ca” aca时,最短含 t t t序列前缀长 4 4 4。而当 t t t串为 “ a ∗ ∗ c a ” “a**ca” aca时,最短含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个小写英文字母以外,还会跟有一些正整数。
在读入字符串时,这些数字表示它前面字母或者"*"重复的次数。
例如 a 5 b c ∗ 3 a5bc*3 a5bc3,表示 “ a a a a a b c ∗ ∗ ∗ ” “aaaaabc***” aaaaabc。输入的正整数不含前导0。

输入描述

第一行输入一个仅包含 26 26 26个小写英文字母的字符串 s s s
第二行输入一个正整数 n n n表示, t t t串的数目。
接下来输入 n n n
再输入一行一个字符串 t t t,表示压缩后的查询串。查询串仅包含 26 26 26个小写英文字母,星号 ′ ∗ ′ '*' ,以及数字。

输出描述

对于每一个查询,如果至少存在一个 s s s的前缀满足 “ “ 最短含 t t t序列串 ” ” 的定义,请输出 s s s的最短含 t t t序列前缀的长度对 998244353 998244353 998244353取余数后的结果。
否则请输出 “ − 1 ” “-1” 1表示无解。

分析

本题采用一种叫 “ “ 序列自动机 ” ” 的玄学操作进行初始化:

	for(i=1;i<=n;i++)
	{
		for(j=0;j<27;j++) s[j][i]=s[j][i-1];
		++s[T[i]-'a'+1][i],++s[0][i];
	}

其中 j j j表示字符( 0 0 0表示 ∗ * ), i i i表示当前序列元素的下标, s [ j ] [ i ] s[j][i] s[j][i]表示在第 i i i位之前出现了 s [ j ] [ i ] s[j][i] s[j][i] j j j
这样初始化完了之后,我们模拟一下,就快乐AC!

#include <bits/stdc++.h>
using namespace std;
const int N=100012,mod=998244353;
int n,m,i,j,l,r,q,v,rem,now,x,res,ne,s[27][N],ans=0;char T[N];bool fl;
int ABS(int x){if(x>=mod) return x-mod; if(x<0) return x+mod; return x;}
int main()
{
	scanf("%s %d",T+1,&q),n=strlen(T+1);
	for(i=1;i<=n;i++)
	{
		for(j=0;j<27;j++) s[j][i]=s[j][i-1];
		++s[T[i]-'a'+1][i],++s[0][i];
	}//序列自动机
	while(q--)
	{
		scanf("%s",T+1),m=strlen(T+1),ans=0,now=1,fl=1;
		for(i=1;i<=m;i=r+1)
		{
			v=(T[i]=='*') ? 0:(T[i]-'a'+1);
			if(!s[v][n]){fl=0;break;}r=i,rem=1,res=0;
			if(i+1<=m&&T[i+1]>='0'&&T[i+1]<='9')
			{
				rem=0,l=i+1;
				while(r+1<=m&&T[r+1]>='0'&&T[r+1]<='9') ++r;
				x=s[v][n];
				for(i=l;i<=r;i++) rem=rem*10+(T[i]-'0'),res=(10ll*res+rem/x)%mod,rem%=x;//大数除法
				res=1ll*res*n%mod;
				if(!rem) rem=x,res=ABS(res-n);
			}//处理数字并顺带取模
			if(s[v][n]-s[v][now-1]<rem) res=ABS(res+n-now+1),rem-=s[v][n]-s[v][now-1],now=1;
			ne=lower_bound(s[v]+now,s[v]+n+1,s[v][now-1]+rem)-s[v]+1;
			res=ABS(res+ne-now),now=ne,ans=ABS(ans+res);
		}
		if(fl) printf("%d\n",ans);
		else puts("-1");
	}
}

----原创文章,仅供参考

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值