【洛谷】刷题代码详解day2

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值