ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

字符串入门学习笔记

2022-09-05 20:30:43  阅读:197  来源: 互联网

标签:入门 int long ++ trie 笔记 ans 字符串 define


字符串哈希

idea

将字符串映射成一个数值(称为哈希值),因此可以在O(1)时间内做到例如判断两个串是否相等这样的事情,优化了时间复杂度

注意,哈希值不同时字符串一定不同;哈希值相同时字符串可能不同,称为冲突

发生冲突的概率是很小的 (how?待补充)

应用

  • 解决字符串匹配问题
  • 求最长回文子串(二分+字符串哈希)

放一道二分+哈希例题来说明字符串哈希具体做法吧:2022牛客多校第五场G题

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pb push_back
using namespace std;
const int N = 5e5+10, seed = 31;
int t, n, m;
char s[N];
int prek[N], pref[N], prec[N];
ll ansk, ansf, ansc;
ull h[N], rh[N], base[N];

inline ull Hash(int l, int r) {
	return h[r] - h[l - 1] * base[r - l + 1];
}

inline ull rHash(int l, int r) {
	return rh[l] - rh[r + 1] * base[r - l + 1];
}

inline bool check(int x, int r) {
	return Hash(x - r + 1, x) == rHash(x, x + r - 1);
}

inline bool check2(int x, int r) {
	return Hash(x - r + 1, x) == rHash(x + 1, x + r);
}

void init() {
	base[0] = 1;
	for (int i = 1; i <= n; i++) base[i] = base[i - 1] * seed;
	h[0] = 0;
	for (int i = 1; i <= n; i++) {
		h[i] = h[i - 1] * seed + s[i] - 'a';
	}
	rh[n + 1] = 0;
	for (int i = n; i >= 1; i--) {
		rh[i] = rh[i + 1] * seed + s[i] - 'a';
	}
}

void solve() {
	//ji
	for (int i = 1; i <= n; i++) {
		int l = 1, r = min(i, n - i + 1), mid, ans;
		while (l <= r){
			mid = (l + r) >> 1;
			if (check(i, mid)) {
				l = mid + 1;
				ans = mid;
			}
			else r = mid - 1;
		}
		ansk += prek[ans + i - 1] - prek[i - 1];
		ansf += pref[ans + i - 1] - pref[i - 1];
		ansc += prec[ans + i - 1] - prec[i - 1];
	}
	//ou
	for (int i = 1; i < n; i++) {
		if (s[i] != s[i + 1]) continue;
		int l = 1, r = min(i, n - i), mid, ans = -1;
		while (l <= r){
			mid = (l + r) >> 1;
			if (check2(i, mid)) {
				l = mid + 1;
				ans = mid;
			}
			else r = mid - 1;
		}
		if(ans == -1) continue;
		ansk += prek[ans + i] - prek[i];
		ansf += pref[ans + i] - pref[i];
		ansc += prec[ans + i] - prec[i];
	}
}

int main(){
    cin >> n;
	scanf("%s", s + 1);
	init();
	for (int i = 1; i <= n; i++) {
		prek[i] = prek[i-1] + (s[i] == 'k');
		pref[i] = pref[i-1] + (s[i] == 'f');
		prec[i] = prec[i-1] + (s[i] == 'c');
	}
	solve();
	printf("%lld %lld %lld\n", ansk, ansf, ansc);
    system("pause");
    return 0;
}

字符串匹配

单串匹配

kmp算法

点击学习 转自oiwiki,讲得真的太好了

主要思想大概是,首先对模式串(t串)进行预处理,处理出 \(nex[]\) 数组,\(nex[i] = x 表示满足 s[0...x-1] 与 s[i-x+1,i] 完全相同的最大的x\)
然后对串 \(s\) 和 \(t\) 进行匹配,如果失配时不把t串又从头开始匹配,而是不断从 \(j\) 回到 \(nex[j]\) 处,直到\(t[j]\) 可以和 \(s[i]\) 匹配
时间复杂度 \(O(m + n)\)

点击查看代码
// #pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pb push_back
using namespace std;
const int N = 1005;
char s[N], t[N];
int n, m;
int nex[N];
int ans;

void init(){
	nex[0]=nex[1]=0;
	int j=0;
	for(int i = 2; i <= m; i++){
		while(j>0&&t[i]!=t[j+1]) j=nex[j];
		if(t[i]==t[j+1]) j++;
		nex[i]=j;
	}
}

void kmp(){
	int j=0;
	for(int i = 1; i <= n; i++) {
		while(j>0&&s[i]!=t[j+1]) j=nex[j];
		//移动可以理解为 固定s串,t串往后挪一段,使得j位置之前与s串仍然是匹配的 
		if(s[i]==t[j+1]) j++;
		if(j==m){
			// printf("%d\n",i-m+1);  //求t在s中的位置 
			j=0;    // 继续求
			ans++;
		}
	}
}
 

int main() {
	while(scanf("%s", s + 1) == 1) {
		if(s[1] == '#') break;
		scanf("%s", t + 1);
		n = strlen(s + 1);
		m = strlen(t + 1);
		ans = 0;
		init();
		kmp();
		printf("%d\n", ans);
	}

	system("pause");
	return 0;
}

多串匹配

AC自动机

点击查看代码(模板)
// #pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pb push_back
using namespace std;
const int N = 1e6 + 10;
int T, n, m;
char s[N], t[N];
int cnt, trie[N][33], fail[N], countt[N];

//ac自动机
// p1的fail指针能连到p2 当且仅当 [root('s son)...p2] 的一段序列是[root...p1]的一段前缀

void insert(char *s, int rt) {
	int len = strlen(s);
	for(int i = 0; i < len; i++) {
		int now = s[i] - 'a';
		if(!trie[rt][now]) {
			trie[rt][now] = ++cnt;
		}
		rt = trie[rt][now];
	}
	countt[rt]++;  //串的个数
}

void GetFail() {
	fail[1] = 0;
	for(int i = 0; i < 26; i++) trie[0][i] = 1;
	queue<int> q;
	q.push(1);
	while(!q.empty()) {
		int now = q.front(); q.pop();
		for(int i = 0; i < 26; i++) {
			if(trie[now][i]) {
				fail[trie[now][i]] = trie[fail[now]][i];
				q.push(trie[now][i]);
			} 
			else {
				trie[now][i] = trie[fail[now]][i];  //失配 trie[][] 回到 1(根)
			}
		}
	}
}

void query(char *t) {
	int rt = 1;
	int ans = 0;
	int len = strlen(t);
	for(int i = 0; i < len; i++) {
		int now = t[i] - 'a';
		int k = trie[rt][now];
		while(k > 1 && countt[k] != -1) {  // k=1 失配 回到根
			ans += countt[k];
			countt[k] = -1;
			k = fail[k];
		} 
		rt = trie[rt][now];   // 沿着建好的trie树往下
	}
	printf("%d\n", ans);
}

int main(){
    scanf("%d", &n);
	cnt = 1;
	for(int i = 1; i <= n; i++) {
		scanf("%s", s);
		insert(s, 1);
	}
	GetFail();
	scanf("%s", t);
	query(t);
    system("pause");
    return 0;
}

回文串

求回文串的数量

1.二分 + 字符串哈希

做法是枚举回文串的中心字符,由于注意到回文串的(从中心字符往两边看的字符串都是回文的)这一性质,那么可以二分以该字符为中心的回文串的最大长度
时间复杂度 \(O(nlogn)\)

2.Manacher

时间复杂度\(O(n)\)

点击查看代码(模板)
// #pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pb push_back
using namespace std;
const int N = 3e7 + 10;
int t, n, m;
char s[N], st[N];
int p[N];
int ans;

// Manacher 
// 求最长回文串长度

void init() {
    n = strlen(st);
    int j = 2;
    s[0] = '^';
    s[1] = '#';
    for(int i = 0; i < n; i++) {
        s[j++] = st[i];
        s[j++] = '#';
    }
    s[j] = '&';
    n = j;
}

void Manacher() {
    init();
    for(int i = 0, mid = 0, r = -1; i < n; i++) {
        p[i] = (i >= r) ? 1 : min(r - i, p[mid * 2 - i]);
        while(s[i + p[i]] == s[i - p[i]]) 
            p[i]++;
        if(i + p[i] > r) {
            r = i + p[i];
            mid = i;
        }
    }
    for(int i = 0; i < n; i++) {
        ans = max(ans, p[i] - 1);
    }
    printf("%d\n",ans);
}

int main(){
    scanf("%s", st);
    Manacher();
    system("pause");
    return 0;
}

求所有回文串

回文自动机 (PAM)

点击查看代码(模板)
// #pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pb push_back
using namespace std;
const int N = 5e5 + 10;
int t, n, m;
char s[N];
int fail[N]; ///失配后转移的位置 
int len[N];
int sz, cnt, last;
int ch[N][33];
int num[N];

// 回文自动机 PAM

void init() {
	s[0] = '^';  //不会出现在字符串中的字符,表示边界
	len[1] = -1; // 1:奇根 0:偶根
	fail[0] = 1;
	cnt = 1;
}

int main(){
    scanf("%s", s + 1);
	n = strlen(s + 1);
	init();
	for(int i = 1; i <= n; i++) {
		int j;
		while(s[i] != s[i - len[last] - 1]) {
			last = fail[last];
		}
		if(!ch[last][s[i] - 'a']) {
			len[++cnt] = len[last] + 2;
			j = fail[last];
			while(s[i] != s[i - len[j] - 1]) {  // last失配后下一个转移到的位置 
				j = fail[j];
			}
			fail[cnt] = ch[j][s[i] - 'a'];
			ch[last][s[i] - 'a'] = cnt;
		}
		last = ch[last][s[i] - 'a'];
	}
	puts("");
//    system("pause");
    return 0;
}

标签:入门,int,long,++,trie,笔记,ans,字符串,define
来源: https://www.cnblogs.com/re0acm/p/16650632.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有