字符处理
坑
- 多组输入,看清题目
总结和关键
- 预处理的几种用法
- 区间和(计算下标i到j的和):使用前缀数组, sum[i, j]=A[j] - A[i - 1];
- 字符子串使用kmp的next数组
- 字符子序列使用序列自动机,既一个二维数组next【n】【26】,n为长度,26为26个字母,数组的意义是下标为i的字符其对于最近的26个字母下标(1.初始化从后向前初始化,这个可以从相邻的字母比较得出,2.下标从1开始,区分于0表示没有字母,3.区分子串和子序列)
- 字符串hash的使用,以及在这个前提下,字符串切割可以转换成为前缀和的处理,字符串循环同构可以转换成2*长数组的处理
- kmp的书写
- 单调队列和栈的维护,以及结合题目使用,两个都与最值相关
题号
-
PIPIOJ 1026 a+b问题(根据题意模拟)
-
PIPIOJ 1343 PIPI的字符串问题Ⅰ(序列自动机)
-
PIPIOJ 1039 重复子序列问题(序列自动机)
-
PIPIOJ 1344 PIPI的字符串问题Ⅱ(KMP)
-
PIPIOJ 1345 PIPI的字符串问题Ⅲ(字符串HASH)
-
PIPIOJ 1346 PIPI的字符串问题Ⅳ(字符串HASH)
-
PIPIOJ 1347 PIPI的序列问题Ⅰ(单调队列)
-
PIPIOJ 1351 小鱼比可爱Ⅱ(单调栈)
-
PIPIOJ 1034 字典序最小的子序列(作为作业)
-
PIPIOJ 1348 PIPI的序列问题Ⅱ(作为作业)
思路与技巧
-
1343
- 对于子序列问题使用序列自动机,初始化从后向前,下标从1开始,从而是没有字母为0,不会混淆
-
1039
- 同样上题
- 但是需要注意问题分解为多个串与母串的比较
- 这题有个坑是多组输入
-
1344
-
使用kmp(注意初值-1等等)
-
与上题的区别除了在子串和子序列上,还有就是对于问题的分解上,这题同样是两个串比较可以拆分为多个子串和串比较,但是这题需要注意的是,拆分时候,可以利用next数组直接打到初始化的目的。(aaaaa和aa比较,应该是存在4个子串与aa相同,如果没有正确在两次子问题中初始化,会导致出现答案为2的错误答案)
int kmp(){ int i = 0, j = 0; int ans = 0; while(i < lenS){ while(i < lenS && j < lenT){ if(j == -1 || s[i] == t[j]){ if(j + 1 == lenT){//一次子问题结束 ans ++;//记录 j = Next[j];//初始化,正好利用kmp的next数组,直接打到目的,区分上一题 }else{ i ++; j ++; } }else j = Next[j]; } } return ans; }
-
-
1345/1346
- 字符hash,类似将a隐射到1, z到26这样,字符串直接对应一个数字,两个字符的比较直接变成值的比较
- 利用预处理后,前缀和,快速剪裁出字符比如abcde的cde,直接从A【5】-A【2】*对应的权 就可以得出
- 1345判断回文串,利用左右两次前缀和
- 1346判断循环同构,既abababab中有几个abab的循环同构,利用将abab*2,既abababab,这样计算hash值,会自动包含所有的循环同构,比如abab【下标1-4】,baba【下标2-5(两倍的意义就在于可以用这个5)】
- 几个计算细节
- 关于hash
- 计算的值可以比较大,需要使用typedef unsigned long long ull;
- 考虑使用机制base=2333;而不是10,对应的权使用数组可以简化计算
- 字符【l,r】=hs[r] - hs[l - 1] * pw[r - l + 1];
- a对应1而不是0,这样做的意义在于使用字符串对应的hash,a和ab有区别
- 关于hash
-
1347
- 使用单调队列,需要自己维护,使用rear和head,不需要循环队列,两端均可以插入删除
- 其次这个限制长度为m,与之前的题目不同就在,这样的话,如果过长,删除最前的数组元素时,需要注意到要保持每个子问题中都是正的,比如:10 7 -9 8,删除10后,7和-9不再成正数,不应该出现这样的前缀了,所以这题用之前思路会很难写
- 使用前缀和以及单调队列一下就简化了问题
-
1351
- 使用单调栈,还是自己维护
- 需要注意的是从前向后初始化而不是从后向前,这样做是因为需要找到最近的大于