P3375 【模板】KMP字符串匹配
KMP算法的关键就是如何求解next数组以及有了next数组之后如何操作字符串
具体详细操作以及详细解释见下文代码
#include <stdio.h>
#include <string.h> //用到其中的strlen函数
char s1[1000010],s2[1000010]; //分别表示待匹配串和模式串
int next[1000010]; //next数组
int t1,t2,len1,len2; //t1,t2分别表示s1,s2中的位置,len1,len2分别表示s1,s2长度
void getNext(); //求模式串的next数组
void KMP(); //和待匹配串匹配
int main() {
scanf("%s",s1);
scanf("%s",s2);
len1=(int)strlen(s1);len2=(int)strlen(s2);
getNext();
KMP();
for(int i=1;i<=len2;i++){ //输出next数组中1-len2
printf("%d ",next[i]);
}
return 0;
}
void getNext(){ //如何求next数组
//根据s2求next数组,存在next数组里面
//代码求next数组和手写next数组还是不太一样的
t1=0;t2=-1;next[0]=-1; //结束时,就求出了1-len2下标的next数
while(t1<len2){
if(t2==-1||s2[t1]==s2[t2]) //如果匹配成功
next[++t1]=++t2;
else //如果匹配不成功
t2=next[t2];
}
}
void KMP(){ //有了next数组,如何匹配
t1=0;t2=0;
while(t1<len1){
if(t2==-1||s1[t1]==s2[t2]){ //匹配成功
t1++;t2++;
}
else //匹配失败
t2=next[t2];
if(t2==len2){ //成功匹配到的时候,将t2变成next[t2],开启新一轮的匹配
printf("%d\n",t1-len2+1);
t2=next[t2]; //继续下一步匹配
}
}
}
倍增法球最近公共祖先LCA
least common ancestor
在看下面的代码前
请先选择观看以下视频,了解链式前向星
-
与邻接表类似
废话不多说,直接上代码
具体理解看代码中的注释!!
//输入格式
//第一行包含三个正整数 N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。
//接下来 N-1行每行包含两个正整数 x,y 表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。
//接下来 M 行每行包含两个正整数 a,b,表示询问 a 结点和 b 结点的最近公共祖先。
//输出格式
//输出包含 M 行,每行包含一个正整数,依次为每一个询问的结果。
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;
const int maxn=5000010; //为了达到数据的要求,5000010能够保证所有数据都在
int n,m,s; //n个节点,m次询问,s是根节点的序号
int tot=0; //要插入的边的序号,也就是新边的一个序号,本质上就是一个计数器
int head[maxn],d[maxn],p[maxn][21]; //head数组可以认为是邻接表操作中指向firstarc构成的一个数组
//d数组存放每个点的深度,p存放每个点的父节点
//p[i][j]表示从i节点向上的2的j次方父节点
struct node{
int v,next; //由于树上不需要权重,所以只需要两个值,v是这条边的终点,next同起点的是下一条边
}edge[maxn*2]; //由于要使用无向边,所以边数乘2
void add(int u,int v){ //加入新边的操作,起点为u,终点为v
edge[tot].v=v; //新边的终点
edge[tot].next=head[u]; //新边的next,类似于邻接表中的头插法,整个过程类似于在链表中插入一个节点
head[u]=tot++; //修改head中的值之后,tot++,最后head数组里存放的是一条边的序号
}
void dfs(int u,int fa){ //LCA的预处理过程,u是孩子,fa是u的父节点
d[u]=d[fa]+1; //更新深度d数组
p[u][0]=fa; //更新p数组,u向上1个父节点是fa
for(int i=1;(1<<i)<=d[u];i++) //1<<i表示2的i次方,深度要在范围内
p[u][i]=p[p[u][i-1]][i-1]; //u向上的2的i次方父节点是u向上的2的i-1次方父节点的i-1次方父节点
for(int i=head[u];i!=-1;i=edge[i].next){
//从u节点对应的节点开始,遍历所有相邻的边
int v=edge[i].v; //一棵树中每个节点只有一个父节点,相邻的边中,除了父节点剩下的都是子节点,也就是u就是剩下所有点的父节点了
if(v!=fa) //不是父节点的其他的所有的点
dfs(v,u);
} //通过递归,将所有点的d和p初始化
}
int LCA(int a,int b){ //求a和b的最近公共祖先
if(d[a]>d[b]) //始终保持a的深度<=b的深度
swap(a,b);
for(int i=20;i>=0;i--) //从2的20次方开始遍历
if(d[a]<=d[b]-(1<<i)) //a和b的深度差>=2的i次方
b=p[b][i];
if(a==b) //如果a和b是一个节点了,那么最近公共祖先就是a了
return a;
for(int i=20;i>=0;i--){ //如果a和b不是一个节点,就说明a和b的最近公共祖先不是a或者b
if(p[a][i]==p[b][i])
continue; //continue就是为了防止出现超过的现象
else
a=p[a][i];b=p[b][i]; //同时向上走相同的深度
}
return p[a][0]; //找到最后a值的数字
}
int main(){
memset(head,-1,sizeof(head)); //将head初始化,不指向任何一条边
int a,b;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<n;i++){
scanf("%d%d",&a,&b); //输入边的信息
add(a,b); //添加边,构造树
add(b,a);
}
dfs(s,0); //初始化d,p数组,从根节点开始初始化,假设它的父节点是0
for(int i=1;i<=m;i++){ //根据不同的数据,查找LCA
scanf("%d%d",&a,&b);
printf("%d\n",LCA(a,b));
}
return 0;
}
输入输出样例
输入 #1
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
输出 #1
4
4
1
4
4