2017ICPC沈阳网络赛 A-string string string 后缀数组 + 线段树

**在这里插入图片描述**
题意:给定你一个字符串s,然后问你在s的所有子串中,有多少子串在s中恰好出现了k次?

思路:~~好像是后缀自动机模板题,但是我木有学过 ~~
还可以用后缀数组加上线段树区间查询的操作来解决。
我们这样想,假如我们对于字符串的每一个后缀i去找在排名上和它相差k-1个的后缀,然后求出这两个后缀的的最长公共前缀长度为L,也就是区间[i+1,i_k-1]的height数组最小值。那么我们就可以知道对于这长度为L的子串,是在s串中出现过至少k次的,但是我们这道题目要求的是出现次数恰好为k次的子串, 所以我们需要计算一下排名差为k+1的后缀的最长共前缀,但是这个差有两个,区间[i,i+k-1]和区间[i+1,i+k],但是减去这两个区间的时候会减重,所以还要再加上区间[i,i+k]这段区间的长度。一开始求出来的是>=k,然后是求出>=k+1减去即可,但是求两次相减多减了>=k+2的情况,再加回来。

再就是对于k等于1的情况,特判一下,这种情况下,区间的左右端点用上面的方法是错误的。这样想,对于一个位置在i处长度为L的后缀字符串,求出height[i]和height[i-1]的最大值,用长度减去即可,这段后缀贡献的答案即为相减所得。

比如suffix[sa[i]] = “abcad”,suffix[sa[i-1]] = “abcd”,suffix[sa[i+1]] =
“ab”,显然height[i] = 3,height[i+1] = 2,len(suffix[sa[i]]) =
5,那么对于sa[i]这个后缀的前缀对答案的贡献为2,即"abca"和"abcad"这两个子串的贡献

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
const int MAXN = 1e5 + 7;
char str[MAXN];

/***Suffix_Array****/
int ans,n,m,x[MAXN],y[MAXN],c[MAXN],sa[MAXN],rk[MAXN],height[MAXN];
//!!! m = 字符集大小

void SA_build(){
	//初始化的计数排序
	for(int i = 0;i < m;i ++) c[i] = 0;
	for(int i = 0;i < n;i ++) c[x[i]=str[i]]++;
	for(int i = 1;i < m;i ++) c[i] += c[i-1];
	for(int i = n-1;i >= 0;i --) sa[--c[x[i]]] = i;

	for(int k = 1;k <= n;k <<= 1){
		int p = 0;
		for(int i = n-k;i < n;i ++) y[p++] = i;
		for(int i = 0;i < n;i ++)
			if(sa[i] >= k) y[p++] = sa[i] - k;
		for(int i = 0;i < m;i ++) c[i] = 0;
		for(int i = 0;i < n;i ++) c[x[i]]++;
		for(int i = 1;i < m;i ++) c[i] += c[i-1];
		for(int i = n-1;i >= 0;i --) sa[--c[x[y[i]]]] = y[i];

		swap(x,y);
		p = 1;
		x[sa[0]] = 0;
		for(int i = 1;i < n;i ++)
			x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&((sa[i-1]+k>=n?-1:y[sa[i-1]+k])==(sa[i]+k>=n?-1:y[sa[i]+k]))?p-1:p++;
		if(p > n) break;
		m = p;
	}
}

/*******************/
void height_build(){
	for(int i = 0;i < n;i ++) rk[sa[i]] = i;
	int k = 0;height[0] = 0;
	for(int i = 0;i < n;i ++){
		if(!rk[i]) continue;
		if(k) k--;
		int j = sa[rk[i]-1];
		while(i+k<n&&j+k<n&&str[i+k]==str[j+k]) k++;
		height[rk[i]] = k;
	}
}

int tree[MAXN<<2];
void pushup(int rt){
	tree[rt] = min(tree[rt<<1],tree[rt<<1|1]);
}
void build(int rt,int l,int r){
	if(l == r){
		tree[rt] = height[l-1];
		return ;
	}
	int mid = (l+r)>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	pushup(rt);
}
int query(int rt,int l,int r,int L,int R){
	if(l >= L && r <= R) return tree[rt];
	int mid = (l+r)>>1;
	int ans = inf;
	if(L <= mid) ans = min(ans,query(rt<<1,l,mid,L,R));
	if(R > mid) ans = min(ans,query(rt<<1|1,mid+1,r,L,R));
	return ans;
}

int main()
{
	int t,k;
	scanf("%d",&t);
	while(t--){
		m = 200;//这里m要初始化 因为在求sa数组的时候m变了
		memset(height,0,sizeof(height));
		scanf("%d",&k);
		scanf("%s",str); 
		n = strlen(str);
		SA_build();
		height_build();
		build(1,1,n);
		// for(int i = 0;i < n;i ++) printf(i == n-1?"%d\n":"%d ",sa[i]);
		// for(int i = 0;i < n;i ++) printf(i == n-1?"%d\n":"%d ",height[i]);
		if(k > n-1) { puts("0");continue; }
		int ans = 0;
		if(k == 1){
			for(int i = 0;i < n;i ++){
				ans += max(0,n-sa[i]-max(height[i],height[i+k]));
			}
		}
		else if(k > 1){
			for(int i = 1;i + k - 1 <= n;i ++){
				ans += query(1,1,n,i+1,i+k-1);
				ans -= query(1,1,n,i,i+k-1);
				if(i + k <= n){
					ans -= query(1,1,n,i+1,i+k);
					ans += query(1,1,n,i,i+k);
				}
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值