CSP-S中的字符串相关算法

注:LP = Luogu ProblemMP = MarsOJ ProblemAP = Acwing Problem

子串一定连续,子序列不一定连续。

常用 C 库函数(仅适用于 C 风格字符串 char 数组):strlen, strcmp, strcat, sscanf, sprintf

strcat 把第二个字符串拼到第一个的结尾。

sscanf():第一个参数为读取内容,后续参数与正常 scanf 相同。

 char s[] = "apple 5 13";
 char t[10]; int a, b;
 sscanf("%c %d %d", t, &a, &b);

常用 C++ 库函数(仅适用于 std::string):+, find(ch, start), substr(start, len), append(s), replace(pos, n, s), erase(pos, n), insert(pos, s), c_str()

find(ch, start) 查找从 start 开始的字符 ch 的位置。

substr(start, len) 注意第二个参数是子串长度不是子串末尾。第二个参数不填则默认为取到字符串结尾。

c_str() 应用场景:把 std::string 转成 C 风格字符串,再使用 printf 进行输出。

 string s, t; int a, b;
 printf("%s=%d&%s+%d", s.c_str(), a, t.c_str(), b);

模式匹配问题(KMP):判断字符串 s2 是否为字符串 s1 的子串。要搞清楚 bordernxt 数组。

border:字符串最大公共前后缀。最大公共前后缀不能是字符串本身(真前缀、真后缀)。

nxt[i]:前缀 i 的 border 长度。怎么快速地求 nxt 数组呢?递推!

s[i+1] == s[nxt[i]+1]nxt[i+1] = nxt[i]+1

s[i+1] != s[nxt[i]+1],找前缀 i 的所有公共前后缀:

 int j = nxt[i];
 while (j!=0 && s[i+1] != s[j+1]) j=nxt[i];
 if (s[i+1] == s[j+1]) nxt[i+1] = j+1;

KMP 复杂度为 O(n+m)。KMP 是个很抽象的算法。必须要画图,抽象,再充分理解。

KMP 应用 AP4678:求字符串的最小周期(最后一个周期不一定循环完)。答案为 n-nxt[n]。画图理解。

例题 MP3308:给定字符串 S,T,问 T 最少由几个 S 的前缀拼接而成。(AT_abc257_g

贪心解(正解一):每次匹配 S,T 的最长公共前缀,匹配成功,就从 T 中删去该前缀,直到不能删为止。删完,删的次数即为最优解;删不完,-1。

DP 解:dp[i] 表示把 T_1-T_i 可以分割成最少的多少个 S 的前缀。dp_i=\min(dp_j+1),其中 j<i 且 T 的第 j+1 到第 i 位是 i 的前缀。

DP 优化(正解二):反证法易得 DP 单调不降。发现 DP 过程中就是要维护以 S_i 结尾,和 T 前缀相同的最大长度。假设 KMP 枚举到 s_i 时,匹配长度为 len,则 dp_i \leftarrow dp_{i-len}。

字符串哈希Hash:和比较有关的字符串题目,第一时间想字符串哈希!

在 O(n) 预处理后,O(1) 判断两个子串是否相等。(映射值相同,两字符串大概率相同)

把字符串看作 k 进制数(k\geq 128),利用数学方法取出当中的一段值,转成十进制,就是那一段的哈希值。

怎么快速算出中间一段对应的 k 进制数的十进制?

计算所有前缀对应的哈希值 hash[i],递推式 has[i] = (has[i-1] * k + s[i]) % mod;

区间 [l,r] 对应的字符串的哈希值(前缀和思想)(has[r]-has[l-1]*qpow[k,r-l+1] % mod + mod) % mod

如果这个数太大怎么办?对一个大质数取余(998244353,10^9+7,10^9+9)

冲突怎么办?如果 n\leq 10^5,无需考虑;如果 n\geq 10^6,做双哈希:一个模 998244353,一个模 10^9+7,如果两个字符串的两个哈希值对应相等,就判定为相等,大大降低判断错误的概率,无需担心呢。

Hash应用最长公共子串。给定 m 个总长不超过 n 的字符串,查找所有字符串的最长公共子串。

答案具有单调性(存在长度为 k 的,必定存在长度为 k-1 的公共子串),二分子串长度 len。判断答案时,O(n) 枚举每一个字符串长度为 len 的子串,并求出哈希值。看这些哈希值的交集是否为空集。

Trie树(字典树):前缀数据结构,用于存储字符串集合。节点没有权值,树边有权值(权值是字符串中的字符)用一棵树形结构维护字符串集合。字符串 = 根节点到叶子节点的路径。就是 26 叉树。

Trie 应用 MP3350:已知 m 条信息,n 条暗号,问对于每条暗号 j,有多少条信息 i 与其有相同前缀,且前缀长度为两个字符串长度的最小值。解法:创建关于暗号的 Trie 树,在字典树上维护 cnt[u] 表示经过节点 u 的字符串个数,拿着每一条暗号在上面跑,走到一个节点就把 cnt[u] 加入答案。

Manacher 算法求最长回文子串。动态规划思想。

显然,每一个回文串都会有一个“回文中心”。显然可以枚举回文中心,二分求解“最大回文半径”,哈希判断左半段与右半段是否相等即可。回文中心可以是一个字符,也可以是两个字符中间的空隙。O(n\log n)。

由于回文中心可能是空隙,还得奇偶处理,很麻烦,所以给字符串两两字符中间以及串首尾出插入一个未出现过的字符,如 #。设 d_i 表示以新串 i 位置为回文中心的回文串的最大半径,即处理前回文串长度。答案 \max d_i。

考虑递推/DP求 d_i。

 char s[N], ch[N];
 int m, n;
 void manachar() {
     int mxr = 0, id;
     for (int i = 1; i <= n; i ++) {
         if (mxr >= i) p[i] = min(mxr - i, p[2*id-i]);
         else p[i] = 0;
         while (ch[i+p[i]+1]==ch[i-p[i]-1]) p[i]++;
         if(p[i]+i>mxr)id=i,mxr=p[i]+i;
     }
 }
 void init() {
     m = strlen(s + 1);
     n = 2*m+1;
     ch[0]='(';
     for(int i=1;i<=m;i++) {
         ch[2*i-1]='#';
         ch[2*i]=s[i];
     }
     ch[n]='#';
     ch[n+1]=')';
 }
  • 25
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值