字符串杂项

两个不怎么常见的算法

1.Z函数

定义:z[i]表示以i开头的后缀与整个串最长的公共串长度

        求法:假设i以前的字符串已经求完,记录l,r为右端最大的区间 [i,i+z[i]-1]

        若 i<r ,则根据定义,z[i]=z[i-l]

        但是i-l>r的情况下不能保证正确性,需要暴力判断

        否则就暴力求出z函数值

用处(其实kmp都可以实现,只不过有些用z函数更直观)

1.匹配字符串

对于两个字符串跑z函数的过程,若z函数值等于模式串的长度就有一次成功匹配

其实可以将两个串粘一起(模式串+其他字符+文本串)然后跑一遍就够了

s=' '+s2+'&'+s1+'*';
for(int i=2;i<=len;i++){
	if(i<=r) z[i]=min(z[i-l+1],r-i+1);
	while(i+z[i]<len&&s[i+z[i]]==s[z[i]+1]) z[i]++;
	if(i+z[i]-1>r) l=i,r=i+z[i]-1;
}

2.找出循环节

根据z函数的性质,对于每一个前缀(1~i),在字符串中出现次数为 字符串杂项

那么找到字符串长度的倍数,再判断i+z[i+1]是否为字符串长度就可以了

例题

发现有一个为 (AB)^i 的式子,就可以用z函数来处理

每次枚举AB的长度,然后奇偶分析

若i为奇数,那么C中出现奇数字符个数恒等于C最短的时候

若i为偶数,那么C中出现奇数字符的个数恒等于原串的个数

预处理出来每个位置前缀答案和后缀答案,用树状数组维护一下就行了

#include<bits/stdc++.h>
#define int long long
using namespace std;
struct trsz{
	int w[35];
	void init(){
		memset(w,0,sizeof(w));
	}
	int lowbit(int x){
		return x&(-x);
	}
	void insert(int id){
		id++;
		while(id<=30) w[id]++,id+=lowbit(id);
	}
	int query(int id){
		int ans=0;
		id++;
		while(id) ans+=w[id],id-=lowbit(id);
		return ans;
	}
};
trsz ty;
char s[1100100],ch;
int t;
int len,cnt[30],pre[1100100],now,sub[1100100];
int z[1100100],l,r;
long long ans;
signed main(){
	scanf("%lld",&t);
	ch=getchar();
	while(t--){
		ch=getchar();
		len=0;
		while(ch>='a'&&ch<='z') s[++len]=ch,ch=getchar();
		memset(cnt,0,sizeof(cnt));
		now=0;
		for(int i=1;i<=len;i++){
			cnt[s[i]-'a']++;
			now+=((cnt[s[i]-'a']&1)?1:-1);
			pre[i]=now; 
		}
		memset(cnt,0,sizeof(cnt));
		now=0;
		for(int i=len;i>=1;i--){
			cnt[s[i]-'a']++;
			now+=((cnt[s[i]-'a']&1)?1:-1);
			sub[i]=now; 
		} 
		l=r=0;
		z[1]=len;
		for(int i=2;i<=len;i++){
			if(i<=r) z[i]=min(z[i-l+1],r-i+1);
			while(i+z[i]<=len&&s[i+z[i]]==s[z[i]+1]) z[i]++;
			if(i+z[i]-1>r) l=i,r=i+z[i]-1;
		}
		ans=0;
		ty.insert(pre[1]);
		for(int i=2;i<len;i++){
			int gs=z[i+1]/i+1;
			int c_beg=i*gs+1;
			
			while(c_beg>len) c_beg-=i,gs--;
			if(!(gs&1)) c_beg-=i;
			ans+=(ty.query(sub[c_beg])*((gs+1)/2)+ty.query(sub[1])*(gs/2));
			ty.insert(pre[i]);
		}
		printf("%lld\n",ans);	
		for(int i=0;i<=len;i++) sub[i]=pre[i]=z[i]=0;
		ty.init();
	}
	return 0;
}

2.manacher

处理回文字符串最简单也是最优的方法

求法:记录一个数组d[i]表示长度为奇数的回文串长度的半径

        先考虑求出d

        假设i以前的字符串已经求完,记录l,r为右端最大的区间 [i-d[i]+1,i+d[i]-1]

        若i<r,d[i]=d[l+r-i],若i+d[i]>r,超出部分用暴力

        否则直接暴力

        那么还有偶数长度的回文串怎么办?

        将字符串中间插入无关字符(#s#t#y#h#)像这样

        d[i]-1为回文串长度,#为中心匹配出来的就是偶数长度了

复杂度分析:z函数与manacher一样,都是将r从1暴力推向n的过程,故复杂度为O(n)

	for(int i=1;i<=a;i++){
		if(r<=i){
			l=r=i;
			while(s[l-1]==s[r+1]) l--,r++;
			d[i]=r-i+1;
		}else{
			int ii=l+r-i;
			d[i]=min(d[ii],r-i+1);
			while(s[i+d[i]]==s[i-d[i]]) d[i]++;
			if(i+d[i]-1>r) r=i+d[i]-1,l=i-d[i]+1;
		}
	}

上一篇:知识点案例回顾总结


下一篇:FCC_React_将 this 绑定到 Class 方法上