HDU 6194 string string string 后缀数组 + RMQ(线段树)

传送门:HDU6194

题意:问给定字符串中有多少种出现k次的子串。

思路:首先想到后缀数组经典问题,求出现不少于k次的子串的最大长度,类似的这题肯定就是在height数组上搞事情啦。

将height数组每相邻k - 1个一组,这k - 1个height[i]中最小的那个设为tmp,就表示这排名相邻的k个后缀的最长公共前缀(设为s)长度为tmp,可以得出s出现了至少k次,那么长度为tmp - 1,tmp - 2 ...1的s的前缀必定也出现了至少K次,要确定这些串是否严格出现了k次,还要判断这相邻k - 1个height的前一个height值和后一个height值,分别设为height[i] 和 height[j],则长度为 1 -- max(height[i], height[j]) 的s的前缀出现了至少k + 1次,因此要去掉这部分。

对于每相邻k - 1个一组的height都求一下对答案的贡献就好了。

至于求相邻k - 1个height的最小值, 那就各显神通了,什么ST表,线段树都可以啦(刚刚想起来因为是连续求最小值,单调队列(滑动窗口)好像也可以)。


以上办法只能解决k >= 2的问题,k == 1的时候是无法解决的(可以自己想想height数组的定义),比赛的时候蒟弱就是卡死在这了。。两个小时愣是没想出来k == 1该怎么解。。

赛后突然灵光一现,我们只要求出所有不同子串的个数,然后减去那些出现大于等于两次不就行了么。。前几天还刚做过后缀数组求不同子串个数(传送门:点击打开链接)。。也是后缀经典用法,不多说了,问题是怎么减去出现大于等于两次的串的个数,height数组啊!height数组啊!height数组啊!(注意不要减重了)

代码:

#include<bits/stdc++.h>  
#define ll long long
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1
#define inf 0x3f3f3f3f
using namespace std;  
const int MAXN = 100010;    
int t1[MAXN], t2[MAXN], c[MAXN];    
int ra[MAXN], height[MAXN];  
int sa[MAXN];    
char str[MAXN];   
int n;  
bool cmp(int *r, int a, int b, int l)    
{    
    return r[a]==r[b]&&r[a+l]==r[b+l];
}    
    
void da(char str[], int sa[], int ra[], int height[], int n, int m)    
{    
    n++;    
    int i, j, p, *x = t1, *y = t2;    
    for(i = 0; i < m; i++) c[i] = 0;    
    for(i = 0; i < n; i++) c[x[i]=str[i]]++;    
    for(i = 1; i < m; i++) c[i] += c[i-1];    
    for(i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;    
    for(j = 1; j <= n; j<<=1)    
    {    
        p = 0;    
        for(i = n-j; i < n; i++) y[p++] = i;    
        for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i]-j;    
        for(i = 0; i < m; i++) c[i] = 0;    
        for(i = 0; i < n; i++) c[x[y[i]]]++;    
        for(i = 1; i < m; i++) c[i] += c[i-1];    
        for(i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];    
        swap(x, y);    
        p = 1; x[sa[0]] = 0;    
        for(i = 1; i < n; i++)    
            x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p-1 : p++;    
        if(p >= n) break;    
        m = p;    
    }    
    int k = 0;    
    n--;    
    for(i = 0; i <= n; i++) ra[sa[i]] = i;    
    for(i = 0; i < n; i++)    
    {    
        if(k) k--;    
        j = sa[ra[i]-1];    
        while(str[i+k]==str[j+k]) k++;    
        height[ra[i]] = k;    
    }    
}
int num[MAXN << 2];
void build(int l, int r, int rt)
{
	if(l == r)
	{
		num[rt] = height[l];
		return ;
	}
	int mid = (l + r) >> 1;
	build(lson);
	build(rson);
	num[rt] = min(num[rt << 1], num[rt << 1 | 1]);
}
int query(int L, int R, int l, int r, int rt)
{
	if(L <= l && r <= R)
	{
		return num[rt];
	}
	int mid = (l + r) >> 1, ans = inf;
	if(L <= mid)
	ans = min(ans, query(L, R, lson));
	if(R > mid)
	ans = min(ans, query(L, R, rson));
	return ans;
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int n,k;
    int T;
    cin>>T;
    while(T--)
    {
        scanf("%d",&k);
        scanf(" %s", str);
        n=strlen(str);
        da(str, sa, ra, height, n, 128);
        build(1, n, 1);
        int last = 2;
        ll ans = 0;
        if(k == 1)
        {
        	//ans = 1ll * (n + 1) * n / 2;
        	for(int i = 1; i <= n; i++)
        	{
        		ans += n - sa[i] - height[i];
        		if(height[i] > height[i - 1])
        		ans -= (height[i] - height[i - 1]);
        	}
        	cout << ans << endl;
        	continue;
        }
        for(int i = 2; i <= n; i++)
        {
        	if(i - k + 2 < 2) continue;
        	int tmp = query(i - k + 2, i, 1, n, 1), tmp2;
        	if(i - k + 1 < 2) tmp2 = 0;
        	else tmp2 = height[i - k + 1];
        	if(i + 1 <= n)
        	tmp2 = max(tmp2, height[i + 1]);
        	if(tmp > tmp2)
        	ans += tmp - tmp2;
        }
        printf("%lld\n",ans);
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值