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;
}
posted @ 2025-03-08 07:28  Tighnari  阅读(24)  评论(0)    收藏  举报