--------------by NK SuperGate
字符串相关的算法比较少,主要是KMP,最小表示法和字典树,当然也有一些奇奇怪怪的字符串dp,这里就不讲了
今天是省赛前的最后一天,发现字符串还没复习的我吓出一身冷汗,赶紧写了个字符串的复习博客,突然发现自己好多东西都没复习……赶紧临时抱抱佛脚
最小表示法
顾名思义,最小表示法就是给你一个环状字符串,求出这个字符串从最优位置切开得到最小字典序方案
我们可以先从i位置和j位置开始进行比较,假如说比较到了第k位,这个时候发生了不同,那么我们可以假设a[i+k]<a[j+k]
因为从j到j+k之间任意位置为起点的字符串都要比从i开始的字符串大,因此j至少变为j+k+1才能找到更优的解,a[i+k]>a[j+k]同理
int Express(){
int i=0,j=1,k;
s+=s;
len=s.length();
while(i<len&&j<len){
for(k=0;k<len;k++)
if(s[i+k]!=s[j+k])break;
if(k==len)break;
if(s[i+k]>s[j+k])i+=k+1;
else if(s[i+k]<s[j+k])j+=k+1;
if(i==j)j++;
}
return min(i,j);
}
KMP
KMP算法应该是所有字符串算法中最广为人知的一种求字符串匹配的算法,其中KMP最为精妙的地方就是fail数组的求法
对fail数组的理解有一定难度,我从学kmp到现在才搞懂Kmp的含义
fail[i]的值实际上就是一个字符串中[0...i]的前缀集合和后缀集合的相同元素的长度最大值
照下面的公式算出向后移动的位数:移动位数 = 已匹配的字符数 - 对应的回退值
这里的回退值就是fail数组
int KMP(){
int i,j=-1,ans=0;
fail[0]=-1;
for(i=1;i<len2;i++){
while(j>-1&&B[j+1]!=B[i])j=fail[j];
if(B[j+1]==B[i])j++;
fail[i]=j;
}
j=-1;
for(i=0;i<len1;i++){
while(j>-1&&B[j+1]!=A[i])j=fail[j];
if(B[j+1]==A[i])j++;
if(j==m){//找到了一个匹配位置
ans++;//ans记录匹配数量,A数组此时的匹配成功的起点为i-m+1
j=fail[j];
}
}
return ans;
}
字典树
字典树是一种高效的字符串数据结构,它利用了字符串的公共前缀来进行储存工作和查找工作,大大提升了运行效率
在一些字符串dp中,我们可以运用字典树来优化dp
在一些计算异或值最大的题中,我们可以构造一个01字典树,然后贪心选取一些位置使得这些位置代表的数相反的对数最大
总之字典树很有用……
void Insert(string a){
int i,len=a.length(),p=0;
for(i=0;i<len;i++){
int t=a[i]-'a';
if(!trie[p].Next[t]){
p=trie[p].Next[t]=++tot;
trie[tot].num=0;
}
else p=trie[p].Next[t];
}
trie[p].num++;//这里的Num记录这种字符串的出现次数
}
void Delete(string a){
int i,len=a.length(),p=0;
for(i=0;i<len;i++){
int t=a[i]-'a';
if(!trie[p].Next[t])return ;
p=trie[p].Next[t];
}
if(trie[p].num>0)trie[p].num--;//删除操作
}
int Find(string a){
int i,len=a.length(),p=0;
for(i=0;i<len;i++){
int t=a[i]-'a';
if(!trie[p].Next[t])return -1;
p=trie[p].Next[t];
}
return trie[p].num;//这里的find返回寻找的a的数量
}
总结
字符串算法虽然不多但是还是很容易出错,有很多细节要注意
字符串是我的弱项……因为平常考试很少涉及到,就算涉及到了也不是特别难,所以考试的时候做到字符串的时候一定要细心细心再细心,做不来也绝对不能慌,还是应该自己再看几遍一些字符串的模板算法