成都集训-字符串篇
[NOI2014]动物园
题目描述
我们给定一个字符串
思路点拨
我们考虑一个暴力,我们可以使用
- 倍增
因为全部的
- 基于树上倍增的进一步优化
实际上,在一般的树上上述的倍增方法已经是足够优秀了,但是这个
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-f;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int MAXN=1e6+10,mod=1e9+7;
int T,n,p[MAXN],f[MAXN];
string s;
signed main(){
T=read();
while(T--){
cin>>s;n=s.length();
memset(p,0,sizeof(p));
memset(f,0,sizeof(f));
for(int i=1;i<n;i++){
int j=p[i];
while(j&&s[i]!=s[j]) j=p[j];
j+=(s[i]==s[j]);
p[i+1]=j;
}
for(int i=1;i<=n;i++)
f[i]=f[p[i]]+1;
int ans=1,j=0;
for(int i=1;i<n;i++){
while(j&&s[i]!=s[j]) j=p[j];
j+=(s[i]==s[j]);
while(j>(i+1)/2) j=p[j];
ans=ans*(f[j]+1)%mod;
}
cout<<ans<<endl;
}
return 0;
}
BZOJ 1461 (Luogu Cow Patterns G)
题目描述
我们有两个字符串,一个是文本串,另一个是模式串。我们认为两个文本串相等当且仅当两个字符串离散化之后相等。希望直到模式串在文本串中出现的位置。
思路点拨
考虑
具体的正确性证明也十分简单,因为我们的前驱后继是在模式串的基础上定义的而不是在文本串上定义的,所以匹配的时候不会有问题。
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-f;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int MAXN=1e6+10,mod=1e9+7;
int n,k,S,a[MAXN];
int b[MAXN],pre[MAXN],suc[MAXN];
struct node{
int pos,val;
bool friend operator<(const node &A,const node &B){
if(A.val==B.val) return A.pos<B.pos;
return A.val<B.val;
}
};
set<node> s;
set<node>::iterator it;
bool equal_ofa(int x,int y){//比较 a[x] 和 b[y]
if(pre[y]){
if(b[pre[y]]<b[y]&&a[x-(y-pre[y])]>=a[x]) return 0;
if(b[pre[y]]==b[y]&&a[x-(y-pre[y])]!=a[x]) return 0;
}
if(suc[y]){
if(b[suc[y]]==b[y]&&a[x+(suc[y]-y)]!=a[x]) return 0;
if(b[suc[y]]>b[y]&&a[x+(suc[y]-y)]<=a[x]) return 0;
}
return 1;
}
bool equal_ofb(int x,int y){//比较 b[x]和 b[y]
if(pre[y]){
if(b[pre[y]]<b[y]&&b[x-(y-pre[y])]>=b[x]) return 0;
if(b[pre[y]]==b[y]&&b[x-(y-pre[y])]!=b[x]) return 0;
}
if(suc[y]){
if(b[suc[y]]==b[y]&&b[x+(suc[y]-y)]!=b[x]) return 0;
if(b[suc[y]]>b[y]&&b[x+(suc[y]-y)]<=b[x]) return 0;
}
return 1;
}
int fail[MAXN];
int id[MAXN],cnt;
signed main(){
n=read(),k=read(),S=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=k;i++){
b[i]=read();
s.insert((node){i,b[i]});
it=s.lower_bound((node){i,b[i]});
it++;
if(b[(*it).pos]>=b[i]&&(*it).pos<i) suc[i]=(*it).pos;
--it;--it;
if(b[(*it).pos]<=b[i]&&(*it).pos<i) pre[i]=(*it).pos;
}
for(int i=2;i<=k;i++){
int j=fail[i-1];
while(j&&!equal_ofb(i,j+1)) j=fail[j];
j+=equal_ofb(i,j+1);
fail[i]=j;
}
for(int i=1,j=0;i<=n;i++){
while(j&&!equal_ofa(i,j+1)) j=fail[j];
j+=equal_ofa(i,j+1);
if(j==k){
++cnt;
id[cnt]=i-k+1;
j=fail[j];
}
}
cout<<cnt<<endl;
for(int i=1;i<=cnt;i++) cout<<id[i]<<endl;
return 0;
}
[POI2005] SZA-Template
题目描述
你打算在纸上印一串字母。
为了完成这项工作,你决定刻一个印章。印章每使用一次,就会将印章上的所有字母印到纸上。
同一个位置的相同字符可以印多次。例如:用 aba
这个印章可以完成印制 ababa
的工作(中间的 a
被印了两次)。但是,因为印上去的东西不能被抹掉,在同一位置上印不同字符是不允许的。例如:用 aba
这个印章不可以完成印制 abcba
的工作。
因为刻印章是一个不太容易的工作,你希望印章的字符串长度尽可能小。
思路点拨
本题具体有两种做法,失配树和动态规划。这里讲述更好理解的失配树做法,想要了解动态规划做法可以看 这里 。
我们考虑建出失配树,然后寻找一些性质。对于一个印章,我们肯定需要在
我们接着想,一个答案什么时候合法?对于一个失配树上的节点
如果我们从
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-f;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int MAXN=5e5+10,mod=1e9+7;
int n,fail[MAXN];
string s;
int temp[MAXN],top;
int pre[MAXN],suc[MAXN];//双向链表
int mx=1;//最大的邻值之差
vector<int> e[MAXN];//失配树
void erase(int x){
if(x<1||x>n) return ;
mx=max(mx,suc[x]-pre[x]);
suc[pre[x]]=suc[x];
pre[suc[x]]=pre[x];
}
void bfs(int f,int v){
queue<int> q;
q.push(f);
while(!q.empty()){
int x=q.front();
q.pop();
if(x==v) continue;
erase(x);
for(int i=0;i<e[x].size();i++){
int to=e[x][i];
q.push(to);
}
}
}
signed main(){
cin>>s;n=s.length();
s='0'+s;
for(int i=2,j=0;i<=n;i++){
while(j&&s[i]!=s[j+1]) j=fail[j];
j+=(s[i]==s[j+1]);
fail[i]=j;
}//KMP
for(int i=n;i;i=fail[i]) temp[++top]=i;//此时temp中失是降序的
for(int i=1;i<=n;i++){
pre[i]=i-1,suc[i]=i+1;
e[fail[i]].push_back(i);
}//树根为0
for(int i=top;i;i--){
bfs(temp[i+1],temp[i]);
if(mx<=temp[i]){
cout<<temp[i];
return 0;
}
}
cout<<n;
return 0;
}
[BZOJ 2601] Country
题目描述
有
我们保证这些字符串的定义是无环的。现在给定了一个小写字母组成的模式串,问其在某个字符串变量中出现的次数。字符串变量的长度和模式串长度 单个 不超过
思路点拨
当一个字符串变量只有小写字母时,我们只需要做
那么两个
定义状态
定义状态
状态转移是十分显然的。当我们在字符串变量扫到的字母是大写时,递归求解。反之我们直接
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-f;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int MAXN=1e2+10,S=30;
const int mod=10000;
int n,len[S],fail[MAXN],m;
char s[MAXN];//s1是文本串,s2是待匹配串
char c[S][MAXN],txt[MAXN];
void init(){
scanf("%s",s+1);
m=strlen(s+1);
for(int i=2;i<=m;i++){
int j=fail[i-1];
while(j&&s[i]!=s[j+1]) j=fail[j];
j+=(s[i]==s[j+1]);
fail[i]=j;
}
}
int f[S][MAXN],pos[S][MAXN];
//f[i][j]表示在字符串 i 开始的 j 位匹配模式串的结果
//nxt[i][j]表示在字符串 i 开始的 j 位匹配模式串后的fail指针
void dp(int i,int j){
if(f[i][j]!=-1) return ;
f[i][j]=0;
int id=j;
for(int k=1;k<=len[i];k++){
if('A'<=c[i][k]&&c[i][k]<='Z'){//遇到大写字母,递归求解
dp(c[i][k]-'A',id);
f[i][j]=(f[i][j]+f[c[i][k]-'A'][id])%mod;
id=pos[c[i][k]-'A'][id];
}
else{
while(id&&c[i][k]!=s[id+1])
id=fail[id];
id+=(c[i][k]==s[id+1]);
if(id==m){
f[i][j]=(f[i][j]+1)%mod;
id=fail[id];
}
}
}
pos[i][j]=id;
}
signed main(){
scanf("%lld",&n);
scanf("%s",txt);
for(int i=0;i<n;i++){
scanf("%s",c[i]);
len[i]=strlen(c[i]);
for(int j=2;j<len[i];j++)
c[i][j-1]=c[i][j];
len[i]-=2;
}
init();//预处理fail指针
memset(f,-1,sizeof(f));
dp(txt[0]-'A',0);
cout<<f[txt[0]-'A'][0];
return 0;
}
[NOIP2020] 字符串匹配
题意描述
对于一个字符串
更具体地,我们可以定义
并递归地定义
则小 C 的习题是求
思路点拨
可以发现
时间复杂度
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-f;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int base=123;
const int MAXN=2e6+10,N=2e6;
int T,n,pw[MAXN]={1},h[MAXN];
char c[MAXN];
int hsh(int l,int r){
int len=r-l+1;
return h[r]-h[l-1]*pw[len];
}
int cnt[30],pre[MAXN],suc[MAXN];
void init(){
memset(cnt,0,sizeof(cnt));
int sum=0;
for(int i=1;i<=n;i++){
if(cnt[c[i]-'a']&1) sum--;
else sum++;
cnt[c[i]-'a']++;
pre[i]=sum;
}
sum=0;
memset(cnt,0,sizeof(cnt));
for(int i=n;i;i--){
if(cnt[c[i]-'a']&1) sum--;
else sum++;
cnt[c[i]-'a']++;
suc[i]=sum;
}
}
int t[30],ans;
int lowbit(int x){
return x&(-x);
}
void add(int x,int y){
for(int i=x+1;i<=27;i+=lowbit(i))
t[i]+=y;
}
int query(int x){
int sum=0;
for(int i=x+1;i;i-=lowbit(i))
sum+=t[i];
return sum;
}
signed main(){
T=read();
for(int i=1;i<=N;i++) pw[i]=pw[i-1]*base;
while(T--){
scanf("%s",c+1);
n=strlen(c+1);
for(int i=1;i<=n;i++)
h[i]=h[i-1]*base+c[i];
init();
memset(t,0,sizeof(t));
ans=0;
for(int len=1;len<=n;len++){
if(len-1) add(pre[len-1],1);
for(int i=1;i+len-1<=n;i+=len){
if(hsh(i,i+len-1)!=h[len]) break;
if(i+len-1<n) ans+=query(suc[i+len]);
}
}
cout<<ans<<endl;
}
return 0;
}
[NOI2011] 阿狸的打字机
题目描述
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有 B
、P
两个字母。经阿狸研究发现,这个打字机是这样工作的:
- 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
- 按一下印有
B
的按键,打字机凹槽中最后一个字母会消失。 - 按一下印有
P
的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。
例如,阿狸输入 aPaPBbP
,纸上被打印的字符如下:
a
aa
ab
我们把纸上打印出来的字符串从
思路点拨
多模式串,多文本串的字符串匹配问题,考虑
在线做是比较难的,考虑将询问离线下来。对于一组询问
代买实现比较简单,总体时间复杂度是
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-f;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int MAXN=1e5+10;
string s;
int n;
int pos[MAXN],trie[MAXN][26],tot;
vector<int> G[MAXN];//字典树
struct node{
int fail,sum,dad;
}t[MAXN];
struct problem{
int u,v,id;//u在v中出现了多少次
//id是问题编号
};
int ans[MAXN];
vector<problem> pb[MAXN];//存储的问题
vector<int> e[MAXN];//fail树
void init(){
int rot=0,cnt=0;
for(int i=0;i<s.length();i++){
if('a'<=s[i]&&s[i]<='z'){
char c=s[i]-'a';
if(!trie[rot][c]){
trie[rot][c]=++tot;
G[rot].push_back(tot);
}
t[trie[rot][c]].dad=rot;
rot=trie[rot][c];
}
else if(s[i]=='P'){
t[rot].sum++;
pos[++cnt]=rot;
}
else rot=t[rot].dad;
}
}
queue<int> q;
void build(){
for(int i=0;i<26;i++)
if(trie[0][i])
q.push(trie[0][i]);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=0;i<26;i++){
int v=trie[u][i];
if(v){
t[v].fail=trie[t[u].fail][i];
q.push(v);
}
else trie[u][i]=trie[t[u].fail][i];
}
}
for(int i=1;i<=tot;i++)
e[t[i].fail].push_back(i);
}
int res,dfn[MAXN],siz[MAXN];
void dfs1(int x){
dfn[x]=++res;
siz[x]=1;
for(int i=0;i<e[x].size();i++){
int to=e[x][i];
dfs1(to);
siz[x]+=siz[to];
}
}
int bit[MAXN];
int lowbit(int x){
return x&(-x);
}
void add(int x,int y){
for(int i=x;i<=res;i+=lowbit(i))
bit[i]+=y;
}
int query(int x){
int cnt=0;
for(int i=x;i;i-=lowbit(i))
cnt+=bit[i];
return cnt;
}
void dfs2(int x){
add(dfn[x],1);
for(int i=0;i<pb[x].size();i++){
int v=pos[pb[x][i].u];
ans[pb[x][i].id]=query(dfn[v]+siz[v]-1)-query(dfn[v]-1);
}
for(int i=0;i<G[x].size();i++){
int to=G[x][i];
dfs2(to);
}
add(dfn[x],-1);
}
signed main(){
cin>>s;
init();//建立字典树
build();
dfs1(0);
n=read();
for(int i=1;i<=n;i++){
int u=read(),v=read();
pb[pos[v]].push_back((problem){u,v,i});
}
dfs2(0);//这是在字典树上
for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
return 0;
}
Substrings in a String
题目描述
你需要维护一个文本串,支持如下操作:
- 将文本串的第
个字符变成 。 - 给定一个模式串,查询其在文本串的
中出现的次数。
思路点拨
我们直接bitset。
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-f;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int MAXN=1e5+10;
char c[MAXN];
int n,q;
bitset<MAXN> temp;
bitset<MAXN> pos[26];
signed main(){
scanf("%s",c+1);
n=strlen(c+1);
for(int i=1;i<=n;i++)
pos[c[i]-'a'][i]=1;
q=read();
while(q--){
int opt=read();
if(opt==1){
int p=read();
char ch;cin>>ch;
pos[c[p]-'a'][p]=0;
c[p]=ch;
pos[c[p]-'a'][p]=1;
}
else{
int l=read(),r=read();
string s;cin>>s;
int len=s.length();
temp.set();
for(int i=0;i<len;i++)
temp=temp&(pos[s[i]-'a']>>i);
printf("%d\n",(s.length()>r-l+1)?0:(temp>>l).count()-(temp>>(r-s.length()+2)).count());
//[l,r] 是我需要的区间,但是我们最终维护的temp数组只记录的字符串匹配的起点
//所以我们令len为字符串长度,那么我们在最终的temp中只需要考虑[l,r-len+1]
//我们先将bitset右移l,提取出[l,n],接着右移 r-len+2位,提取出我们需要的区间
//但是当字符串的长度大于询问区间的时候,bitset中任然可能有存留(l>r-len+1) ,我们需要特判,不然WA on 25
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现