一道字符串练习题

9 篇文章 0 订阅
4 篇文章 0 订阅

题意:

求一个字符串有多少本质不同的子串,这里的不同指两个子串的最小表示法不同.

数据范围:

n < = 5 e 4 , 1 < = a [ i ] < = n , 其 中 n 是 字 符 串 长 度 , a [ i ] 是 字 符 n<=5e4,1<=a[i]<=n,其中n是字符串长度,a[i]是字符 n<=5e4,1<=a[i]<=n,n,a[i]

首先,如果不同指字符串本身不同,那么有经典做法:先后缀排序,求出排名相邻的后缀的lcp,然后就等于总字符串个数-所有相邻后缀的lcp的长度.
但是现在不同的定义不同,我们考虑更改hash的定义,变成"每个位置到下一个与其相同的位置的距离" ∗ w i *w^i wi,其中 w w w是步长,然后就可以用线段树维护一个子串的hash值了,就可以二分+hash求lcp

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e4+5;
typedef unsigned long long ull;
const ull step=233;
inline int read(){
	char c=getchar();int t=0,f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
	return t*f;
}
int n,a[maxn],rt[maxn],lst[maxn],p[maxn],ord[maxn];
ull pw[maxn];
ull sum[maxn<<5],ha;
int ls[maxn<<5],rs[maxn<<5];
map<int,ull> h[maxn];
int cnt=0;
void modify(int &now,int l,int r,int x,int val){
	int e=++cnt;
	ls[e]=ls[now];rs[e]=rs[now];now=e;
	if(l==r){sum[now]=val;return ;}
	int mid=l+r>>1;
	if(x<=mid)modify(ls[now],l,mid,x,val);
	else modify(rs[now],mid+1,r,x,val);
	sum[now]=sum[ls[now]]*pw[r-mid]+sum[rs[now]];
}
void query(int now,int l,int r,int x,int y){
	if(!now){
		int len=min(y,r)-max(x,l)+1;
		ha=ha*pw[len];
		return ;
	}
	if(x<=l&&r<=y){
		int len=r-l+1;
		ha=ha*pw[len]+sum[now];
		return ;
	}
	int mid=l+r>>1;
	if(x<=mid)query(ls[now],l,mid,x,y);
	if(y>mid)query(rs[now],mid+1,r,x,y);
	return ;
}
bool same(int l,int r,int len){
	ull t1,t2;
	if(h[l].count(l+len-1))t1=h[l][l+len-1];
	else {ha=0;query(rt[l],1,n,l,l+len-1);h[l][l+len-1]=t1=ha;}
	if(h[r].count(r+len-1))t2=h[r][r+len-1];
	else {ha=0;query(rt[r],1,n,r,r+len-1);h[r][r+len-1]=t2=ha;}
	return t1==t2;
}
int lcp(int x,int y){
	if(x>y)swap(x,y);
	int p=0;
	for(int i=15;i>=0;i--)if(y+p+(1<<i)-1<=n&&same(x,y,p+(1<<i)))p+=1<<i;
	return p;
}
bool cmp(int x,int y){
	int z=lcp(x,y);
	if(x+z>n)return 1;
	if(y+z>n)return 0;
	int t1=z<p[x+z]?0:p[x+z],t2=z<p[y+z]?0:p[y+z];
	return t1<t2;
}
int main(){
	//freopen("c.in","r",stdin);
	//freopen("c2.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	pw[0]=1;
	
	for(int i=1;i<=n;i++)pw[i]=pw[i-1]*step;
	for(int i=n;i>=1;i--){rt[i]=rt[i+1];
		if(lst[a[i]]){
			modify(rt[i],1,n,lst[a[i]],lst[a[i]]-i);
			p[lst[a[i]]]=lst[a[i]]-i;
		}
		lst[a[i]]=i;
	}
	long long ans=0;
	for(int i=1;i<=n;i++)ord[i]=i;
	stable_sort(ord+1,ord+1+n,cmp);
	for(int i=1;i<=n;i++)ans=ans+n-i+1;
	for(int i=2;i<=n;i++)ans-=lcp(ord[i-1],ord[i]);
	printf("%lld\n",ans);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值