字符串1——KMP,字符串最小表示法,AC自动机

KMP算法:

由于只有一个模式串,所以我们只需要充分利用这个模式串的信息即可

我们考虑一下对于一个匹配串,我们不妨设已经匹配到了模式串的第i个位置,且在匹配串的第j位,如果第i+1个位置不能匹配,那么我们如果用朴素的匹配思想的话是只能跳到第j-i+2为再一位一位去匹配

但是这样的我们并没有利用到该模式串的信息,也没有利用已经匹配那些字符的信息

所以我们就引入了一个东西叫做nxt数组,nxt[i]表示当前模式串以第i个位置为结尾的前缀串的最长后缀的长度满足他是该模式串的一个前缀,也就是如果匹配到第i个位置之后不在匹配了,那么我们就可以直接找到最近的那个起点继续匹配

nxt数组只需要递推就行了

时间复杂度为O(n+m)因为你实际上每次往下匹配就是右移字符串结尾位置,每次利用nxt数组实际上就是右移字符串起始位置,所以线性时间复杂度显然

【模板】KMP字符串匹配

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+10;
int nxt[MAXN];
char a[MAXN],b[MAXN];
int lena,lenb;
void init()
{
	nxt[1]=0;
	for (int i=2;i<=lenb;i++)
	{
	nxt[i]=nxt[i-1];	
	while (b[nxt[i]+1]!=b[i]&&nxt[i]) nxt[i]=nxt[nxt[i]];
	if (b[nxt[i]+1]==b[i]) nxt[i]++; 
	}
}
void solve()
{
	int t=0;
	for (int i=1;i<=lena;i++)
	 {
	 	 while (a[i]!=b[t+1]&&t) t=nxt[t];
	 	 if (a[i]==b[t+1]) t++;
	 	 if (t==lenb) cout << i-lenb+1 << endl;
	 }
}
int main()
{
	scanf("%s",a);
	scanf("%s",b);
	lena=strlen(a);
	lenb=strlen(b);
	for (int i=lena;i>=1;i--) a[i]=a[i-1];
	for (int i=lenb;i>=1;i--) b[i]=b[i-1];
	init();
	solve();
	for (int i=1;i<=lenb;i++) printf("%d ",nxt[i]);
}

字符串最小循环表示法:

给你一个字符串,让你求他的最小循环表示,也就是把这个字符串看做一个环,让你求出从某个位置开始的最小字符串

我们思考一下这个东西怎么求

我们设给定的串为s

我们首先考虑两个指针i,j表示我们考虑起始位置为i,j的字符串,如果满足s[i....i+k-1]=s[j....j+k-1]且s[i+k]!=s[j+k]

这时候我们考虑一下

如果s[i+k]<s[j+k]那么j=j+k+1,否则同理

为什么?

我们考虑一下这个贪心,拿s[i+k]<s[j+k]来说话

如果此时我们发现以j...j+k为开头的字符串一定不可能作为最小循环的起始位置,因为他恰好已经被i....i+k给统统叉掉了

模板:

#include<cstdio>
using namespace std;
const int maxn=30010;
char a[maxn<<1];
int n;
int main()
{
    scanf("%d",&n);
    scanf("%s",a+1);
    for (int i=1;i<=n;i++)
    {
        a[i+n]=a[i];
    }
    int i,j,k;
    for (i=1,j=2;i<=n&&j<=n;)
    {
        for (k=0;k<n&&a[i+k]==a[j+k];k++)
        if (k==n) {
            break;
        } 
        if (a[i+k]<a[j+k])
        {
            j=j+k+1;
            if (i==j) j++;
        }
        else
        {
            i=i+k+1;
            if (i==j) i++;
        }
    }
    printf("%d",i>n?j:i);
}

bzoj1398

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
char s[N],s1[N];
int la(char *a,int n)
{
	 for (int i=1;i<=n;i++)
    {
        a[i+n]=a[i];
    }
    int i,j,k;
    for (i=1,j=2;i<=n&&j<=n;)
    {
        for (k=0;k<n&&a[i+k]==a[j+k];k++)
        if (k==n) {
            break;
        } 
        if (a[i+k]<a[j+k])
        {
            j=j+k+1;
            if (i==j) j++;
        }
        else
        {
            i=i+k+1;
            if (i==j) i++;
        }
    }
    return i>n?j:i;
}
int main()
{
   scanf("%s",s+1);
   int len=strlen(s+1);
   int l1=la(s,len);
   scanf("%s",s1+1);
   int l2=la(s1,len);
   bool p=1;
   for (int i=0;i<len;i++)
   	 if (s[(l1+i-1)%len+1]!=s1[(l2+i-1)%len+1]) {
   	 	p=0; break;
   	 }
   if (!p) printf("%s","No");
   else {
   	printf("%s\n","Yes");
   	for (int i=0;i<len;i++)
   		putchar(s[(l1+i-1)%len+1]);
   } 	 
}

AC自动机:

AC自动机实际上就是一个串的多模式匹配,我们用trie树来整合所有模式串

自然的,fail[x]表示当前状态的字符串的最长后缀使得他是某个模式串的前缀的所指向的状态

然后我们就可以在AC自动机上做一些事情了

关于构建AC自动机(求fail指针)的一些小说明:

1.构建AC自动机时要一层一层往下建

2.在跳fail指针的时候,我们只需要暴力往上跳就行了,这样时间复杂度是线性的

证明:我们可以考虑势能分析,定义h(i)为节点i的势能表示他的字符串长度

我们先取出其中一个模式串进行分析,由于对于i节点它的操作次数ci<=h(i)-(h(fail[i+1])-1)+1=h(i)-h(fail[i+1])+2=h(fail[i])-h(fail[i+1])+3=>ci+h(fail[i+1])-h(fail[i])<=3

综上\sum ci<=2n(博主刚学势能分析,可能有些的丑或者不对的地方,还望提出)

所以时间复杂度为O(n)

所以AC自动机的构造时间复杂度是O(n) 的

(其实时间复杂度证明是与kmp一样的,只是博主我想写一写势能分析)

【模板】AC自动机(简单版)

// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+10;
int sum[MAXN],f[MAXN][26],nxt[MAXN];
char s[MAXN];
int n,len,cnt,ans;
void put(int now,int x)
{
	if (x==len){
		sum[now]++;
		return;
	}
	if (f[now][s[x]-'a']) {
		put(f[now][s[x]-'a'],x+1);
	}
    else {
    	f[now][s[x]-'a']=++cnt;
    	put(cnt,x+1);
	}
}
void make()
{
	queue<int>q;
	q.push(0);
	while (!q.empty()){
		int t=q.front(); q.pop();
		for (int i=0;i<26;i++)
		 if (f[t][i]) {
		 	if (t!=0) nxt[f[t][i]]=f[nxt[t]][i];
		 	q.push(f[t][i]);
		 }
		 else {
		 	f[t][i]=f[nxt[t]][i];
		 }
	}
}
void findans()
{
	int len=strlen(s);
	int now=0;
	for (int i=0;i<len;i++)
	{
		now=f[now][s[i]-'a'];
		int t=now;
		while (t) {
			if (sum[t]==-1) break;
			ans+=sum[t]; sum[t]=-1; t=nxt[t];
		}
	}
}
int main()
{
	memset(f,0,sizeof(f));
	scanf("%d",&n);
	for (int i=1;i<=n;i++){
	scanf("%s",s);
	len=strlen(s);
	put(0,0);
	}
	make();
	scanf("%s",s);
	findans();
	printf("%d\n",ans);
}

[NOI2011]阿狸的打字机

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
struct node{
	int u,to;
};
node edge[MAXN<<1];
int tree[MAXN],dfn[MAXN],fa[MAXN];
vector<pair<int,int> >q[MAXN];
int head[MAXN],size[MAXN];
int k,dfstime,cnt,po[MAXN],n,nxt[MAXN],ans[MAXN],m,x,y;
char s[MAXN];
int f[MAXN][26];
void add(int x,int y)
{
	edge[k].u=y;
	edge[k].to=head[x];
	head[x]=k++;
}
int lowbit(int x)
{
	return x&(-x);
}
void adit(int x,int y)
{
	while (x<=dfstime) {
		tree[x]+=y; x+=lowbit(x); 
	}
}
int find(int x)
{
	int ans=0;
	while (x) {
		ans+=tree[x]; x-=lowbit(x);
	}
	return ans;
}
int dfs(int now)
{
	dfn[now]=++dfstime; 
	size[now]=1;
    for (int i=head[now];i!=-1;i=edge[i].to){
    	int u=edge[i].u;
    	size[now]+=dfs(u);
	}	
	return size[now];
}
void init()
{
	int len=strlen(s);
	int now=0;
	for (int i=0;i<len;i++)
	if (s[i]>='a'&&s[i]<='z')
	{
		if (f[now][s[i]-'a']) {
			now=f[now][s[i]-'a'];
		}
		else {
			f[now][s[i]-'a']=++cnt; fa[cnt]=now; now=cnt;
		}
	}
	else
	if (s[i]=='B')
	{
		now=fa[now];
	}
	else {
		po[++n]=now;
	}
}
void make()
{
	queue<int>q;
	q.push(0);
	while (!q.empty())
	{
		int t=q.front(); q.pop();
		for (int i=0;i<26;i++) 
		if (f[t][i])
		{
			if (t!=0) {
				nxt[f[t][i]]=f[nxt[t]][i];
				add(f[nxt[t]][i],f[t][i]);
			}
			else add(0,f[t][i]);
			q.push(f[t][i]);
		}
		else f[t][i]=f[nxt[t]][i];
	}
}
void solve(){
	int now=0,cnt=0;
	int len=strlen(s);
	for (int i=0;i<len;i++)
	if (s[i]>='a'&&s[i]<='z')
	{
			now=f[now][s[i]-'a'];
			adit(dfn[now],1);
	}
	else
	if (s[i]=='B')
	{
		adit(dfn[now],-1);
		now=fa[now];
	}
	else {
		++cnt;
		for (int j=0;j<q[cnt].size();j++){
			ans[q[cnt][j].second]=find(size[po[q[cnt][j].first]]+dfn[po[q[cnt][j].first]]-1)-find(dfn[po[q[cnt][j].first]]-1);
		}
	}
}
int main()
{
	scanf("%s",&s);
	init();
	scanf("%d",&m);
	memset(head,-1,sizeof(head));
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y); 
		q[y].push_back(make_pair(x,i));
	}
	make();                                    
	dfs(0);               
	solve();                             
	for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
}

[SCOI2012]喵星球上的点名

解析:首先我们对老师点名的串建AC自动机,然后考虑对于每个喵星人的名与姓分别进行匹配,然后对于fail树上的点建一颗虚树,然后只要维护个树上前缀就可以解决第二个问题,对于第一个问题我只能想到树剖这样的算法

// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
const int M=5e4+10;
const int D=18;
int s_la[N],dfn[N],tree[N<<2],dep[N],f[N][19],ans[N],sta[N],s[N],fail[N],sum[N],top[N],dfn_none[N],size[N],son[N];
int n,m,x,y,l,cnt,dfstime,dfstime_none;
vector<int>a[N],b[N];
map<int,int>ch[N];
vector<int>e[N];
bool cmp(int x,int y) {
    return dfn[x]<dfn[y];
}
void pushdown(int k) {
    if (tree[k]) {
        tree[k<<1]+=tree[k]; tree[k<<1|1]+=tree[k];
        tree[k]=0;
    }
}
int findans(int l,int r,int k,int t) {
    if (l==r) return tree[k];
    else {
        int mid=(l+r)/2;
        pushdown(k);
        if (t<=mid) return findans(l,mid,k<<1,t); else return findans(mid+1,r,k<<1|1,t);
    }
}
void change(int l,int r,int k,int ll,int rr,int t) {
    if (l==ll&&r==rr) {
        tree[k]+=t;
    }
    else {
        int mid=(l+r)/2;
        pushdown(k);
//		cout << ll << ' ' << rr << ' ' << k << endl;
        if (rr<=mid) change(l,mid,k<<1,ll,rr,t); 
        else if (ll>mid) change(mid+1,r,k<<1|1,ll,rr,t); 
        else {
            change(l,mid,k<<1,ll,mid,t); change(mid+1,r,k<<1|1,mid+1,rr,t);
        }
    }
}
void Make_AC() 
{
    queue<int>q;
    q.push(1);
    while (!q.empty()) {
        int v=q.front(); q.pop();
        map<int,int>::iterator it;
        it=ch[v].begin();
        for (;it!=ch[v].end();it++) {
            int t=v; t=fail[t];
            while (t) {
                if (ch[t][it->first]) {
                    t=ch[t][it->first]; break;
                } else t=fail[t];
            }
            fail[it->second]=(t?t:1);
            e[fail[it->second]].push_back(it->second);
            sum[it->second]+=sum[fail[it->second]];
            q.push(it->second);
        }	
    }
}
int get_lca(int x,int y) {
    if (dep[x]<dep[y]) swap(x,y);
    for (int i=D;i>=0;i--) if (dep[f[x][i]]>=dep[y]) x=f[x][i];
    if (x==y) return x;
    for (int i=D;i>=0;i--) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
void lalala(int now,int goal) {
    if (top[now]==top[goal]) {
        if (now!=goal) change(1,cnt,1,dfn[goal]+1,dfn[now],1); return;
    } 
    else {
        change(1,cnt,1,dfn[top[now]],dfn[now],1); lalala(f[top[now]][0],goal);
    }
}
void Make_tree(int now) {
    if (l==0) return;
    sort(s+1,s+l+1,cmp);
    int top=0;
    for (int i=1;i<=l;i++) {
        if (top>=1) {
 			int lca=get_lca(sta[top],s[i]);
 			while (dep[lca]<dep[sta[top]]&&top) {
 				top--; if (dep[sta[top]]<dep[lca]) {lalala(sta[top+1],lca),ans[now]+=sum[sta[top+1]]-sum[lca];break;}
                 else {lalala(sta[top+1],sta[top]),ans[now]+=sum[sta[top+1]]-sum[sta[top]];}
             }
             if (sta[top]!=lca) sta[++top]=lca; 
        }
        sta[++top]=s[i];
    }
    lalala(sta[top],1); ans[now]+=sum[sta[top]];
}
int dfs1(int now,int d) {
    int len=e[now].size(); dep[now]=d;
    dfn_none[now]=++dfstime_none;
    size[now]=1;
    for (int i=1;i<=D;i++) f[now][i]=f[f[now][i-1]][i-1];
    for (int i=0;i<len;i++) {
        size[now]+=dfs1(e[now][i],d+1);
        f[e[now][i]][0]=now;
        if (size[son[now]]<size[e[now][i]]) son[now]=e[now][i]; 
    }
    return size[now];
}
void dfs2(int now,int la){
    top[now]=la; dfn[now]=++dfstime;
    if (son[now]) dfs2(son[now],la);
    int len=e[now].size();
    for (int i=0;i<len;i++) {
        int u=e[now][i];
        if (u==son[now]) continue;
        dfs2(u,u);
    }
}
int main() 
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) {
        scanf("%d",&x); 
        for (int j=1;j<=x;j++) {
            scanf("%d",&y); a[i].push_back(y);
        }
        scanf("%d",&x);
        for (int j=1;j<=x;j++) {
            scanf("%d",&y); b[i].push_back(y);
        }
    }
    cnt=1;
    for (int i=1;i<=m;i++) {
        scanf("%d",&x);
        int now=1;
        for (int j=1;j<=x;j++) {
            scanf("%d",&y);
            if (ch[now][y]) now=ch[now][y]; 
            else now=ch[now][y]=++cnt; 
        }
        s_la[i]=now;
        sum[now]++;
    }
    Make_AC();
//	cout << 1 << endl;
    dfs1(1,1);
    dfs2(1,1);
    for (int i=1;i<=n;i++)
     {
     	int len=a[i].size();
     	int now=1;
     	l=0;
     	for (int j=0;j<len;j++) {
     	 	if (ch[now][a[i][j]]) {
     	 		now=ch[now][a[i][j]]; s[++l]=now;      
              }
            else {
                while (now) {
                    now=fail[now]; if (ch[now][a[i][j]]) break;
                }
                if (now) {
                    now=ch[now][a[i][j]];
                    s[++l]=now;  
                } else now=1;
 			}
        }
        len=b[i].size();
        now=1;
        for (int j=0;j<len;j++) {
            if (ch[now][b[i][j]]) {
                now=ch[now][b[i][j]]; s[++l]=now;
            } 
            else {
                while (now) {
                    now=fail[now]; if (ch[now][b[i][j]]) break;
                }
                if (now) {
                    now=ch[now][b[i][j]];
                    s[++l]=now;
                } else now=1;
            }
        }
        Make_tree(i);
     }
    for (int i=1;i<=m;i++) printf("%d\n",findans(1,cnt,1,dfn[s_la[i]]));
    for (int i=1;i<=n;i++) printf("%d ",ans[i]); 
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值