[hdu] P5769 Substring

题意:求一个字符串包含某一个特定字符互不相同的子串个数。

思考过程:
1.如果是求一个字符串所有互不相同的子串个数,这是容易的。因为只需要遍历这个字符串所有后缀,依次计算这个后缀对答案的贡献即可。
遍历所有后缀并且逐个计算贡献是后缀数组解决字符串计数问题的关键。
2.在模板题中,每个后缀的贡献是这个后缀的所有前缀数-lcp中的前缀数,这个问题中每个后缀的贡献是什么?类比后不难发现是后缀的所有前缀数-重复出现的前缀数-不包含特定字符的前缀数,当然有可能某一前缀即重复又不包含特定字符。
如何计算每一个后缀不包含特定字符的前缀数?只需要找到离这个后缀的开头最近的特定字符的位置即可。
3.注意一些细节,比如有可能某一前缀即重复又不包含特定字符,统计答案的时候应该是取二者的并集

#include<bits/stdc++.h>
#define FAST ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define INF 0x3f3f3f3f
typedef long long ll;
const int maxn = 1e5+5;

using namespace std;

int n,m;
char s[maxn],ch;
int height[maxn],dis[maxn];
int sa[maxn],rk1[maxn],tp1[maxn],tax[maxn];
int *rk=rk1, *tp=tp1;

void bucket()
{
	for (int i=0; i<=m; i++) tax[i]=0;
	for (int i=1; i<=n; i++) tax[rk[i]]++;
	for (int i=1; i<=m; i++) tax[i]+=tax[i-1];
	for (int i=n; i>=1; i--) sa[tax[rk[tp[i]]]--]=tp[i];	
}

void get_sa()
{
	m=128;
	for (int i=1; i<=n; i++) rk[i]=s[i],tp[i]=i;
	bucket();
	for (int k=1,p=0; p<n; m=p,k<<=1)
	{
		p=0;
		for (int i=1; i<=k; i++) tp[++p]=n-k+i;
		for (int i=1; i<=n; i++)
			if (sa[i]>k) tp[++p]=sa[i]-k;
		bucket();
		swap(tp,rk);
		rk[sa[1]]=p=1;
		for (int i=2; i<=n; i++)
			rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]] && tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p; 
	}
}

void get_height()
{
	int k=0;
	for (int i=1; i<=n; i++)
	{
		if (k) k--;
		int j=sa[rk[i]-1];
		while(s[i+k]==s[j+k]) k++;
		height[rk[i]]=k;
	}
}

int get_d(int cur)
{
	for (int i=cur+1; i<=n; i++)
		if (s[i]==ch) return i-cur;
	return -1;
}

void get_dis()
{
	dis[0]=get_d(0);
	if (dis[0]==-1)
	{
		for (int i=1; i<=n; i++) dis[i]=-1;
		return;
	}
	for (int i=1; i<=n; i++)
	{
		if (s[i]==ch) dis[i]=0;
		else if (dis[i-1]>=1) dis[i]=dis[i-1]-1;
		else dis[i]=get_d(i);
	}
}

int kase;
int main()
{
   	FAST;
	cin>>kase;
	for (int t=1; t<=kase; t++)
	{
		cin>>ch;
		cin>>s+1;
		n=strlen(s+1);
		get_sa();
		get_height();	
		get_dis();
		
		ll ans=0;
		for (int i=1; i<=n; i++)
		{
			if (dis[i]<0) continue;
			ans+=n-i+1-max(height[rk[i]],dis[i]);
		}
		cout<<"Case #"<<t<<": "<<ans<<endl;
	} 
return 0;
}

反思:
1.我wa了几次的原因是因为在遍历所有后缀的时候:

		for (int i=1; i<=n; i++)
		{
			if (dis[i]<0) continue;
			ans+=n-sa[i]+1-max(height[rk[i]],dis[i]);
		}

这边for循环枚举是从左向右枚举后缀的左端点,而不是字典序,所以后缀的长度不是n-sa[i]+1!!!这反应出我对后缀数组中几个数组的意思没有熟练掌握。

sa[i]=k表示字典序为i的后缀的首字符在原字符串的第k位
rk[i]=k表示在原字符串中以第i位开头的后缀字典序为k
height[i]=k表示字典序为i的后缀与字典序为i-1的后缀的lcp长度

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值