2025.3.8字符串学习笔记
KMP算法
用于子串匹配的算法, 可以知道第一次出现完整子串的位置。
【模板】 KMP
题目描述
给出两个字符串 \(s_1\) 和 \(s_2\),若 \(s_1\) 的区间 \([l, r]\) 子串与 \(s_2\) 完全相同,则称 \(s_2\) 在 \(s_1\) 中出现了,其出现位置为 \(l\)。
现在请你求出 \(s_2\) 在 \(s_1\) 中所有出现的位置。
定义一个字符串 \(s\) 的 border 为 \(s\) 的一个非 \(s\) 本身的子串 \(t\),满足 \(t\) 既是 \(s\) 的前缀,又是 \(s\) 的后缀。
对于 \(s_2\),你还需要求出对于其每个前缀 \(s'\) 的最长 border \(t'\) 的长度。
解决
优先考虑暴力:令主串的下标为 \(i\) ,令子串的下标为 \(j\) ,主串的长度为 \(lena\) ,子串长度为 \(lenb\)。
当 \(a_i = b_j\) 时,同时移动 \(i,j\) 直到不匹配或匹配完成。
若不匹配,则令 \(i=i+1,j=1\) 重新开始下一轮匹配。
代码非常简单就不写了,但是复杂度可以被卡到 \(O(lena\times lenb)\) 。
优化
可以发现上述方式存在许多冗余操作。所以我们可以考虑直接将子串移动到主串的匹配失败时的位置,并且最大限度地保留 \(j\) 。
例如:
主串:abcabdabcabc
子串:abcabc
我们假设下标从 \(1\) 开始。即此时第 \(6\) 位匹配失败。
我们需要将子串移动到主串的 \(4-9\) 。
形如:
主串:abcabdabcabc
子串:\(\space\space\space\space\) abcabc
此处应有图片,但是我没有。
接下来就是代码:
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e6+60;
int kmp[N];
string a,b;
int main(){
cin>>a>>b;
a=' '+a;
b=' '+b;
int lena=a.length()-1;
int lenb=b.length()-1;
int j=0;
for(int i=2;i<=lenb;i++){
while(j>0 && b[j+1]!=b[i]){
j=kmp[j];
}
if(b[j+1]==b[i]) j++;
kmp[i]=j;
}
j=0;
for(int i=1;i<=lena;i++){
while(j>0 && b[j+1]!=a[i]){
j=kmp[j];
}
if(b[j+1]==a[i]){
j++;
}
if(j==lenb){
cout<<i-lenb+1<<'\n';
j=kmp[j];
}
}
for(int i=1;i<=lenb;i++){
cout<<kmp[i]<<' ';
}
return 0;
}
Trie树
【模板】字典树
可以用来查前缀,比较简单易懂。
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=3e6+50;
int ge(char s){
if(s>='0' && s<='9'){
return s-'0';
}else if(s>='A' && s<='Z'){
return s-'A'+10;
}else if(s>='a' && s<='z'){
return s-'a'+36;
}
return -1;
}
struct Trie{
int tr[N][80];
int en[N];
int cnt[N];
int tot=0;
void init(Trie &s){
for(int i=0;i<=tot;i++){
for(int j=0;j<=70;j++){
s.tr[i][j]=0;
}
s.en[i]=0;
s.cnt[i]=0;
}
s.tot=0;
}
void update(string s){
int u=0;
for(int i=0;i<(int)s.length();i++){
int c=ge(s[i]);
if(!tr[u][c]){
tr[u][c]=++tot;
}
u=tr[u][c];
cnt[u]++;
}
en[u]++;
}
int find(string s){
int u=0;
for(int i=0;i<(int)s.length();i++){
int c=ge(s[i]);
if(!tr[u][c]) return 0;
u=tr[u][c];
}
return cnt[u];
}
}tree;
int T,n,m;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
cin>>n>>m;
for(int i=1;i<=n;i++){
string s;
cin>>s;
tree.update(s);
}
for(int i=1;i<=m;i++){
string s;
cin>>s;
cout<<tree.find(s)<<'\n';
}
tree.init(tree);
}
return 0;
}
最大异或对 The XOR Largest Pair
可以处理最大异或值,一直走反的即可(贪心),正确性容易证明。
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
using namespace std;
const int N=3e6+50;
struct Trie{
int tr[N][3];
int tot=0;
void init(Trie &s){
for(int i=0;i<=tot;i++){
for(int j=0;j<=31;j++){
tr[i][j]=0;
}
}
tot=0;
return ;
}
void update(int num){
int u=0;
for(int i=31;i>=0;i--){
int k=(num>>i)&1;
if(!tr[u][k]) tr[u][k]=++tot;
u=tr[u][k];
}
}
int find(int num){
int res=0;
int u=0;
for(int i=31;i>=0;i--){
int k=(num>>i)&1;
if(!tr[u][k^1]){
u=tr[u][k];
}else{
u=tr[u][k^1];
res^=(1<<i);
}
}
return res;
}
}tree;
int n,a[N],ans=0;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
tree.update(a[i]);
}
for(int i=1;i<=n;i++){
ans=max(ans,tree.find(a[i]));
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号