字符串学习笔记

KMP

模板题

OI-wiki上有一个很不错的kmp做法,就是直接把模式串与文本串用特殊符号链接,然后求前缀数组即可
感觉vector可能更舒服(?)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>

using namespace std;

const int N=2000000;
char a[N],b[N];
vector<char>c;
vector<int>kmp;

void solve(){
	scanf("%s%s",a,b);
	int la=strlen(a),lb=strlen(b);
	for(int i=0;i<lb;++i) c.push_back(b[i]);
	c.push_back('\0');
	for(int i=0;i<la;++i) c.push_back(a[i]);
	kmp.push_back(0);
	for(int i=1;i<=la+lb;++i){
		int j=kmp[i-1];
		while(j>0&&c[i]!=c[j]) j=kmp[j-1];
		if(c[i]==c[j]) kmp.push_back(j+1);
		else kmp.push_back(j);
		if(kmp[i]==lb) printf("%d\n",i-2*lb+1);
	}
	for(int i=0;i<lb;++i) printf("%d ",kmp[i]);
}

int main(){
	solve();
	return 0;
}

Manacher

模板题

跟KMP一样,本质都是用已更新状态来减少未知状态的计算

注意边界

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>

using namespace std;

vector<char>a;
vector<int>p;

void init(){
	a.push_back('%');a.push_back('|');
	char c;
	while(scanf("%c",&c)!=EOF){
		a.push_back(c);
		a.push_back('|');
	}
	a.push_back('$');
}

void manacher(){
	init();	p.push_back(1);
	int sz=a.size(),ans=0;
	for(int i=1,mid=0,r=0;i<sz;++i){
		if(i<=r) p.push_back(min(p[mid*2-i],r-i+1));
		else p.push_back(1);
		while(a[i-p[i]]==a[i+p[i]])
			++p[i];
		if(p[i]+i>r) r=p[i]+i-1,mid=i;
		ans=max(ans,p[i]);
	}
	printf("%d\n",ans-1);
}

int main(){
	manacher();
	return 0;
}

回文匹配

KMP+Manacher+二阶前缀和

1.二阶前缀和的数组最前面需要两个0,因为

\[\sum_{i=0}^{n-1}\sum_{j=m/2+1}^{f_i}\sum_{k=i-j+m}^{i+j-1}a_k \]

\[=\sum_{i=0}^{n-1}\sum_{j=m/2+1}^{f_i}(pre_{i+j-1}-pre_{i-j+m-1}) \]

\[=\sum_{i=0}^{n-1}((num_{i+f_i-1}-num_{i+m/2-1})-(num_{i-m/2+m-2}-num_{i-f_i+m-2})) \]

这四项的下届分别相对原来的0进行了-0,-1,-1,-2的处理,所以要流出两个空位置

2.前缀和的\(O(n^2)\)算法中使用循环,不需要考虑下界大于上界的情况,但是二阶前缀和需要!这个问题卡了我一晚上( 任何时候进行前缀和都不要忘了判断上下界的大小。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N=10000000;
string s1,s2;int n1,n2;
int num[N],f[N];

void KMP(){
	string s3=s2+"$"+s1;int sz=n1+n2+1;
	vector<int> kmp;kmp.clear();kmp.push_back(0);
	for(int i=1;i<sz;++i){
		int j=kmp[i-1];
		while(j>0&&s3[i]!=s3[j]) j=kmp[j-1];
		if(s3[i]==s3[j]) kmp.push_back(j+1);
		else kmp.push_back(j);
		if(kmp[i]==n2) num[i-n2-1]++;
	}
	for(int i=1;i<n1;++i) num[i]+=num[i-1];
	for(int i=1;i<n1;++i) num[i]+=num[i-1];
	for(int i=n1+1;i>=2;--i) num[i]=num[i-2];
}

void Manacher(){
	string s4="$"+s1+"%";
	vector<int> p;p.push_back(1);
	for(int i=1,mid=0,r=0;i<n1+2;++i){
		if(i<=r) p.push_back(min(p[mid*2-i],r-i+1));
		else p.push_back(1);
		while(s4[i-p[i]]==s4[i+p[i]]) ++p[i];
		if(i+p[i]>r){
			r=i+p[i]-1;mid=i;
		}
	}
	for(int i=2;i<=n1+1;++i) f[i]=p[i-1];
}

void solve(){
	unsigned int ans=0;num[0]=num[1]=0;
	// for(int i=1;i<=n1;++i)
	// 	for(int j=n2/2+1;j<=f[i];++j)
	// 		ans+=num[i+j-1]-num[i-j+n2-1];
	for(int i=2;i<=n1+1;++i)
		ans+=num[i+f[i]-1]-num[min(i+f[i]-1,i+n2/2-1)]-num[i-n2/2+n2-2]+num[min(i-n2/2+n2-2,i-f[i]+n2-2)];
	cout<<ans<<endl;
}

int main(){
	cin>>n1>>n2>>s1>>s2;
	KMP();Manacher();solve();
	return 0;
}

Trie

于是他错误的点名开始了

两种建树方法,使用数组模拟和直接使用指针(好像没啥区别),使用vector能有效避免数组不知道开多大的问题

//静态数组
#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,m;
struct TRIE1{
	int cnt;
	int trie[1000000][30];
	int end[1000000];

	void insert(string a){
		int u=0,len=a.length();
		for(int j=0;j<len;++j){
			if(!trie[u][a[j]-'a']) trie[u][a[j]-'a']=++cnt;
			u=trie[u][a[j]-'a'];
		}end[u]++;
	}

	void fnd(string a){
		int u=0,len=a.length();
		for(int j=0;j<len;++j){
			int num=a[j]-'a';
			if(!trie[u][num]){
				printf("WRONG\n");return;
			}u=trie[u][num];
		}if(end[u]==1){
			end[u]++;printf("OK\n");
		}else if(end[u]==0) printf("WRONG\n");
		else printf("REPEAT\n");
	}
}Trie;

signed main(){
	cin>>n;for(int i=1;i<=n;++i){
		string tmp;cin>>tmp;Trie.insert(tmp);
	}cin>>m;for(int i=1;i<=m;++i){
		string tmp;cin>>tmp;Trie.fnd(tmp);
	}
	return 0;
}
//vector
#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,m,cnt;
struct node{
	int son[30],end;
	// int fa;
};vector<node>trie;

void build(string a){
	int u=0,len=a.length();
	for(int i=0;i<len;++i){
		int num=a[i]-'a';
		if(!trie[u].son[num]){
			trie[u].son[num]=++cnt;
			trie.push_back({NULL,0});
		}
		u=trie[u].son[num];
	}
	++trie[u].end;
}

void fnd(string a){
	int u=0,len=a.length();
	for(int i=0;i<len;++i){
		int num=a[i]-'a';
		if(!trie[u].son[num]){cout<<"WRONG"<<endl;return;}
		u=trie[u].son[num];
	}
	if(trie[u].end==1) {++trie[u].end;cout<<"OK"<<endl;}
	else if(trie[u].end==0) cout<<"WRONG"<<endl;
	else cout<<"REPEAT"<<endl;
}

signed main(){
	node tmp={NULL,0};
	trie.push_back({NULL,0});
	cin>>n;for(int i=1;i<=n;++i){string tmp;cin>>tmp;build(tmp);}
	cin>>m;for(int i=1;i<=m;++i){string tmp;cin>>tmp;fnd(tmp);}
	return 0;
}

最长异或路径

思路:先将边权异或和最大转换为两个节点到树根的边权异或和异或最大,然后就转换成了n个数任意两个数的异或值最大。对这n个数建01-Trie,然后对每个数进行比较:从Trie的树根开始向下走,每次尽量选择与数字相反的数位进行贪心,最终得到的就是正确答案。复杂度\(O(nlogn)\)

注意:无向图开双倍数组,Trie开30倍数组!

#include <bits/stdc++.h>
using namespace std;

const int N = 400000;
int n, cnt, ans;
struct edge{
	int nxt, v, w; 
}e[N];
int h[N], val[N];
struct trie{
	int son[2] = {0, 0};
}a[4000000];

void add(int u, int v, int w){
	e[++cnt].nxt = h[u];
	e[cnt].v = v;
	e[cnt].w = w;
	h[u] = cnt;
}

void dfs(int u, int fa){
	for(int i = h[u]; i; i = e[i].nxt){
		int v = e[i].v, w = e[i].w;
		if(v == fa) continue;
		val[v] = val[u] ^ w;
		dfs(v, u);
	}
}

void build(){
	cnt = 0;int now;
	for(int i = 1; i <= n; ++i){
		now = 0;
		for(int j = (1 << 30); j; j >>= 1){
			bool x = j & val[i];
			if(!a[now].son[x]) a[now].son[x] = ++cnt;
			now = a[now].son[x];
		}
	}
}

int maxxor(int val){
	int now = 0, ret = 0;
	for(int j = (1<<30); j; j >>= 1){
		bool x = j & val;
		if(a[now].son[!x]){
			now = a[now].son[!x];
			ret += j;
		}
		else now = a[now].son[x];
	}return ret;
}

signed main(){
	cin>>n;
	for(int i = 1; i < n; ++i){
		int u, v, w;cin>>u>>v>>w;add(u, v, w);add(v, u, w);
	}dfs(1, 0);build();
	for(int i = 1; i <= n; ++i){
		ans = max(ans, maxxor(val[i]));
	}cout<<ans<<endl;
	system("pause");
	return 0;
}
posted @ 2024-03-21 12:02  hcx1999  阅读(8)  评论(0编辑  收藏  举报