kmp AcWing 831. KMP字符串
给定一个字符串 𝑆,以及一个模式串 𝑃,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模式串 𝑃 在字符串 𝑆 中多次作为子串出现。
求出模式串 𝑃 在字符串 𝑆 中所有出现的位置的起始下标。
输入格式
第一行输入整数 𝑁,表示字符串 𝑃 的长度。
第二行输入字符串 𝑃。
第三行输入整数 𝑀,表示字符串 𝑆 的长度。
第四行输入字符串 𝑆。
输出格式
共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。
#include<iostream>
using namespace std;
const int N=10010,M=100010;
int n,m;
char p[N],s[M];
int ne[N];
int main()
{
cin >> n >> p+1>> m >> s+1;
// 求next过程
for (int i=2,j=0;i<=n;i++)
{
while (j && p[i]!=p[j+1]) j=ne[j];
if (p[i]==p[j+1]) j++;
ne[i]=j;
}
// kmp匹配过程
for (int i=1,j=0;i<=m;i++)
{
while (j && s[i]!=p[j+1]) j=ne[j];
if (s[i]==p[j+1]) j++;
if (j==n)
{
printf ("%d ",i-n);
j=ne[j];
}
}
return 0;
}
求next数组过程(next函数构建部分):
这是一段关键的代码,用于构建模式字符串p的next数组
初始化:通过for循环,从i = 2开始(因为next[1]通常默认设为0,这里从第二个字符位置开始计算后续的next值),同时设置j = 0,这里j可以看作是一个辅助指针,用于追溯已经匹配过的部分。
回溯与匹配判断:在循环中,while (j && p[i]!=p[j+1]) j=ne[j];这行代码的作用是,当当前字符p[i]与p[j + 1]不匹配时(j不为0表示之前已经有部分匹配了,需要根据next数组回溯到合适的位置继续比较),就通过j = ne[j]不断回溯j的值,直到找到可以继续匹配的位置或者j变为0(表示前面没有可回溯的匹配部分了)。
匹配成功更新:如果p[i]和p[j + 1]相等,说明找到了新的匹配部分,就将j的值加1,表示匹配长度增加了。
记录next值:最后将当前位置i对应的next值记录为j,即ne[i] = j,通过这样的循环,就可以依次计算出模式字符串p每个位置的next值了。
KMP 匹配过程:
这部分代码利用已经构建好的next数组,在主字符串s中进行模式字符串p的匹配操作。
同样通过for循环遍历主字符串s,从i = 1开始,j = 0作为辅助指针,用于记录当前模式字符串p已经匹配到的位置。
回溯与匹配判断:while (j && s[i]!=p[j+1]) j=ne[j];这行代码的逻辑和求next数组时类似,当主字符串s中当前字符s[i]与模式字符串p中当前期望匹配的字符p[j + 1]不匹配时,根据next数组回溯j的值,找到合适的位置继续匹配。
匹配成功更新:如果s[i]和p[j + 1]相等,就将j的值加1,表示模式字符串p在当前位置又多匹配了一个字符。
匹配成功输出与回溯:当j的值等于模式字符串p的长度n时,说明模式字符串p在主字符串s中找到了一次完全匹配,此时通过printf ("%d ",i - n);输出匹配位置(输出的是模式字符串在主字符串中起始位置的下标,所以用i - n),然后通过j = ne[j]回溯j的值,以便继续在主字符串s中查找下一次可能的匹配位置。
树 AcWing 835. Trie字符串统计
维护一个字符串集合,支持两种操作:
“I x”向集合中插入一个字符串x;
“Q x”询问一个字符串在集合中出现了多少次。
共有N个操作,输入的字符串总长度不超过 105,字符串仅包含小写英文字母。
输入格式
第一行包含整数N,表示操作数。
接下来N行,每行包含一个操作指令,指令为”I x”或”Q x”中的一种。
输出格式
对于每个询问指令”Q x”,都要输出一个整数作为结果,表示x在集合中出现的次数。
每个结果占一行。
#include<iostream>
using namespace std;
interestingn
const int N=100010;
z
int son[N][26],cnt[N],idx;
char str[N];
void insert (char str[])
{
int p=0;
for (int i=0;str[i];i++)
{
int u=str[i]-'a';
if (!son[p][u]) son[p][u]=++idx;
p=son[p][u];
}
cnt[p]++;
}
int query (char str[])
{
int p=0;
for (int i=0;str[i];i++)
{
int u=str[i]-'a';
if (!son[p][u]) return 0;
p=son[p][u];
}
return cnt[p];
}
int main()
{
int n;
scanf ("%d",&n);
while (n--)
{
char op[2];
scanf ("%s%s",op,str);
if (op[0]=='I') insert(str);
else printf ("%d\n",query(str));
}
return 0;
}
insert函数:
这个函数用于将输入的字符串插入到字典树中,具体步骤如下:
初始化指针:首先定义int p = 0;,这里p可以看作是一个指针,指向当前正在处理的字典树节点,初始时指向根节点,根节点编号为0。
遍历字符串并构建字典树:通过for (int i = 0; str[i]; i++)循环遍历输入的字符串,对于每个字符:
计算字符对应的数组下标:int u = str[i] - 'a';这行代码将当前字符(假设是英文字母)转换为一个0到25之间的整数,以便能在son数组中找到对应的位置。例如,字符a对应的u就是0,字符b对应的u就是1,依此类推。
判断并创建子节点:if (!son[p][u]) son[p][u] = ++idx;这行代码判断当前节点p是否已经有了对应字符(由u表示)的子节点,如果没有(son[p][u]为0),就创建一个新节点,通过++idx为新节点分配一个唯一的编号,并将这个编号赋值给son[p][u],也就是在字典树中添加了一条从当前节点p到新节点的边。
移动指针到子节点:执行p = son[p][u];,将指针p移动到当前字符对应的子节点上,以便继续处理字符串中的下一个字符,构建字典树的下一层结构。
记录字符串出现次数:当整个字符串遍历完后,也就是完成了该字符串在字典树中的插入过程,通过cnt[p]++;语句将以当前节点p为结尾的字符串出现次数加1,表示这个字符串又被插入了一次(如果是首次插入,初始值为0,加1后变为1)。
query函数:
这个函数用于查询输入字符串在字典树中出现的次数,具体逻辑如下:
初始化指针:同样先定义int p = 0;,让指针初始指向字典树的根节点。
遍历字符串并查找路径:通过for (int i = 0; str[i]; i++)循环遍历输入的字符串,对于每个字符:
计算字符对应的数组下标:和insert函数中一样,通过int u = str[i] - 'a';将字符转换为对应的数组下标。
判断路径是否存在:if (!son[p][u]) return 0;这行代码判断当前节点p是否存在对应字符(由u表示)的子节点,如果不存在,说明字典树中没有这个字符串,直接返回0,表示该字符串未被插入过或者不存在。
移动指针到子节点:若存在对应子节点,则执行p = son[p][u];,将指针p移动到该子节点上,继续沿着字典树往下查找字符串的后续字符。
返回字符串出现次数:当整个字符串遍历完后,说明在字典树中找到了该字符串对应的路径,此时通过return cnt[p];返回以当前节点p为结尾的字符串出现的次数,也就是这个字符串在之前插入操作中出现的频次。
并查集 AcWing 836. 合并集合
—共有n个数,编号是1~n,最开始每个数各自在一个集合中。
现在要进行m个操作,操作共有两种:
1."M a b”,将编号为a和b的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
总通
2."Q a b”,询问编号为a和b的两个数是否在同一个集合中;
输入格式
第一行输入整数n和m。
接下来m行,每行包含一个操作指令,指令为“Ma b”或"Qa b"中的一种。
输出格式
对于每个询问指令"Q a b”,都要输出一个结果,如果a和b在同一集合内,则输出""Yes”,否则输出No”。每个结果占一行。
#include<iostream>
using namespace std;
const int N=100010;
int n,m;
int p[N];
int find (int x)
{
if (p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main()
{
scanf ("%d%d",&n,&m);
for (int i=1;i<=n;i++) p[i]=i;
while (m--)
{
char op[2];
int a,b;
scanf ("%s%d%d",op,&a,&b);
if (op[0]=='M') p[find (a)]=find (b);
else
{
if (find(a)==find(b)) puts ("Yes");
else puts("No");
}
}
return 0;
}
find函数:
这个函数用于查找元素x所在集合的根节点(代表元素),它采用了路径压缩的优化策略,具体步骤如下:
首先判断当前元素x的父节点p[x]是否等于它自身,如果相等,说明x就是所在集合的根节点,直接返回x即可。
如果p[x]不等于x,说明x不是根节点,那就递归地调用find函数去查找p[x]的根节点(也就是x的父节点所在集合的根节点),并且在找到根节点后,将当前元素x的父节点直接更新为这个根节点(通过p[x] = find(p[x]);这行代码实现),这样做的好处是可以在后续查找过程中减少查找路径的长度,优化查找效率,这个操作就是路径压缩。最后返回找到的根节点。
main函数
首先通过scanf ("%d%d", &n, &m);从标准输入读取两个整数,分别赋值给n和m,即元素的总个数和操作的总次数。
接着通过for (int i = 1; i <= n; i++) p[i] = i;循环对并查集数组p进行初始化,将每个元素的父节点都初始化为它自己,表示一开始每个元素都各自属于一个独立的集合。
然后通过while (m--)循环来依次处理每一次操作,在每次循环中:
定义char op[2];用于存储操作指令(只读取一个字符,因为指令要么是M表示合并操作,要么是Q表示查询操作),同时定义int a, b;用于存储操作涉及的两个元素编号,再通过scanf ("%s%d%d", op, &a, &b);读取操作指令以及对应的两个元素编号。
如果操作指令op[0]是M(表示合并操作),执行p[find(a)] = find(b);,先通过find函数分别找到元素a和b所在集合的根节点,然后将元素a所在集合的根节点的父节点设置为元素b所在集合的根节点,这样就把元素a所在的集合和元素b所在的集合合并成了一个集合,实现了集合的合并操作。
如果操作指令op[0]不是M(也就是Q,表示查询操作),则通过if (find(a) == find(b)) puts("Yes");判断元素a和b是否在同一个集合中,即先通过find函数分别找到它们所在集合的根节点,然后比较这两个根节点是否相同,如果相同,说明a和b在同一个集合中,输出Yes;否则输出No。
1210

被折叠的 条评论
为什么被折叠?



