字符串哈希简单题

一:P3501 [POI2010] ANT-Antisymmetry

不难发现,题中让我们所求的实际就是原 01 01 01 串在异或意义下的回文子串数。对于此问题很自然想到 manacher 算法,然而这可是 CSP-S!!!

在想一想,字符串题不先考虑字符串哈希还考虑什么。哈希判断回文串是很方便的,但是对于此题如何找方案数?注意到回文串中 01 01 01 数量相等,即回文串长度为偶数,所以我们可以枚举对称中心,二分找最长回文子串长度,这个就是字符串哈希很经典的操作了。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const int N=5e5+10,B=13331;
int n,a[N],b[N];ll ans;
ull f[N],g[N],power[N];//自然溢出哈希
void cal(int x){
	int l=0,r=min(x,n-x),len=0;
	while(l<=r){
		int mid=(l+r)>>1;
		if(f[x]-f[x-mid]*power[mid]==g[x+1]-g[x+1+mid]*power[mid]) len=mid,l=mid+1;
		else r=mid-1;
	}
	ans+=len;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		scanf("%1d",&a[i]);
		b[i]=a[i]^1;
	}
	power[0]=1;
	for(int i=1;i<=n;i++) power[i]=power[i-1]*B;
	for(int i=1;i<=n;i++) f[i]=f[i-1]*B+(a[i]+11);
	for(int i=n;i>=1;i--) g[i]=g[i+1]*B+(b[i]+11);
	for(int i=1;i<=n;i++) if(a[i]!=a[i+1]) cal(i);//枚举对称中心
	cout<<ans<<endl;
	return 0;
}

二:P3667 [USACO17OPEN] Bovine Genomics G

纯字符串哈希模版。

令斑点牛所属字符串集合为 S a S_a Sa,普通牛所属字符串集合为 S b S_b Sb

鉴于数据范围很小,考虑比较暴力的做法。我们可以暴力枚举全部区间,然后开一个 map,将 S a S_a Sa 中的哈希值存入 map 中,然后遍历 S b S_b Sb 的串,判断是否有重即可。

发现暴力的时间复杂度为 O ( n m 2 l o g n ) O(nm^2log_{}n) O(nm2logn),可能有点悬,再进一步优化一下。考虑二分答案,改为判定。最终优化为 O ( n m l o g n l o g m ) O(nmlog_{}n_{}log_{}m) O(nmlognlogm)。足以通过。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const int N=510,B=13331;
int n,m;char a[N][N],b[N][N];
ull power[N],ha[N][N],hb[N][N];
map<ull,bool> vis;
bool check(int len){
	for(int l=1,r=l+len-1;r<=m;l++,r++){
		bool flag=true;vis.clear();
		for(int i=1;i<=n;i++) vis[ha[i][r]-ha[i][l-1]*power[len]]=true;
		for(int i=1;i<=n;i++){
			if(vis.count(hb[i][r]-hb[i][l-1]*power[len])){flag=false;break;}
		}
		if(flag) return true;
	}
	return false;
}
int main(){
	ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>(a[i]+1);
	for(int i=1;i<=n;i++) cin>>(b[i]+1);
	power[0]=1;
	for(int i=1;i<=m;i++) power[i]=power[i-1]*B;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) ha[i][j]=ha[i][j-1]*B+a[i][j];
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) hb[i][j]=hb[i][j-1]*B+b[i][j];
	int l=0,r=m,ans=0;
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid)) ans=mid,r=mid-1;
		else l=mid+1;
	}
	cout<<ans<<endl;
	return 0;
}

三:P6739 [BalticOI 2014 Day1] Three Friends

一眼顶针,字符串哈希题。不过用 string 截取子串判断也可以。

依然是处理出哈希值,然后枚举被删点。设 f i f_i fi为前缀 1 − i 1-i 1i 的哈希值,对于求子串的哈希值,我们有式子: f r − f l − 1 ∗ b r − l + 1 f_r-f_{l-1}*b^{r-l+1} frfl1brl+1,记 h a s h ( l , r ) hash(l,r) hash(l,r) 为此值。

设区间 [ l , r ] [l,r] [l,r] 中删除了 x x x 上的字符,我们也有式子:
h a s h ( 1 , x − 1 ) ∗ b r − x + h a s h ( x + 1 , r ) hash(1,x-1)*b^{r-x}+hash(x+1,r) hash(1,x1)brx+hash(x+1,r)

然后这道题就做完了,不过还是有些坑点,详见代码。

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
const int N=2e6+10,B=13331;
int n;ull power[N],f[N];
char ch[N];ull h[N];
int hsh(int l,int r){
	return f[r]-f[l-1]*power[r-l+1];
}
int get(int l,int r,int x){
	return hsh(l,x-1)*power[r-x]+hsh(x+1,r);
}
int main(){
	ios::sync_with_stdio(false);
	cin>>n>>(ch+1);
	if(n%2==0){cout<<"NOT POSSIBLE"<<endl;return 0;}
	power[0]=1;
	for(int i=1;i<=n;i++) power[i]=power[i-1]*B;
	for(int i=1;i<=n;i++) f[i]=f[i-1]*B+ch[i];
	int cnt=0,ans=0,len=(n-1)/2;
	for(int i=1;i<=n;i++){
		if(i<=len){
			int l1=get(1,len+1,i),l2=hsh(len+2,n);
			if(l1==l2) cnt++,h[cnt]=l1,ans=i;
		}
		else{
			int l1=hsh(1,len),l2=get(len+1,n,i);
			if(l1==l2) cnt++,h[cnt]=l1,ans=i;
		}
	}
	cnt=unique(h+1,h+1+cnt)-h-1;//注意去重,若字符串一样,插入位置不同,那它们本质也是一样的
	if(cnt==0){cout<<"NOT POSSIBLE"<<endl;return 0;}
	if(cnt>1) cout<<"NOT UNIQUE"<<endl;
	else{
		int res=0;
		for(int i=1;i<=n;i++){
			if(i!=ans) cout<<ch[i],res++;
			if(res==len) break;
		}
	}
	return 0;
}

四:似乎在梦中见过的样子

看到 n n n 的范围,不大也不小, O ( n 2 ) O(n^2) O(n2) 应该是能过的。

那就想 O ( n 2 ) O(n^2) O(n2) 的做法。看到让我们求的串满足 A + B + A A+B+A A+B+A 的形式,前缀和后缀相等,很自然的想到 kmp 算法。我们枚举起点,然后从起点开始跑 kmp,对于每个位置的 nxt 数组,它可能会有前缀和后缀重叠,这并不满足条件,所以可以跳到 nxt[nxt[j]],直到满足条件或判断无解为止。

#include<bits/stdc++.h>
using namespace std;
const int N=15010;
int n,k,nxt[N],ans;char a[N],ch[N];
void work(int x){
	int len=0;nxt[1]=0;
	for(int i=x;i<=n;i++) a[++len]=ch[i];
	for(int i=2,j=0;i<=len;i++){
		while(j&&a[i]!=a[j+1]) j=nxt[j];
		if(a[i]==a[j+1]) j++;
		nxt[i]=j;int p=j;
		while(p>=i-p) p=nxt[p];
		if(p>=k) ans++;
	}
}
int main(){
	cin>>(ch+1),n=strlen(ch+1);cin>>k;
	for(int i=1;i<=n-k-1;i++) work(i);
	cout<<ans<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值