hdu6194 string string string(后缀数组+RMQ)

题目

给你一个串(串长<=1e5),一个k(k>=1)

问串中恰好出现k次的子串有多少个

思路来源

https://www.cnblogs.com/weeping/p/7503972.html

题解

先建后缀数组和high数组,

high[i]代表排名为i和排名为i-1的最长公共前缀

 

注意到出现k次的子串长度的上界为长度为k-1的区间的最小值

而出现k+1次的子串长度的上界为长度为k的区间的最小值

所以一个长度为k-1的区间对答案的贡献,

就是这段区间把出现k+1次及以上的子串刨去的值

 

记RMQ(i+1,i+k-1)为这个区间的high的最小值,

那么[i+1,i+k-1]对答案贡献即max(0,RMQ(i+1,i+k-1)-max(RMQ(i,i+k-2),RMQ(i+1,i+k)))

由RMQ(i,i+k-2)=min(high[i],RMQ(i+1,i+k-1)),RMQ(i+1,i+k)=min(high[i+k],RMQ(i+1,i+k-1))知,

后两段区间最小值,二选一,如果是RMQ(i+1,i+k-1)答案显然为0,是另一个才有意义

所以可以化简成max(0,RMQ(i+1,i+k-1)-max(high[i],high[i+k]))

 

[i+2,i+k]贡献不会与[i+1,i+k-1]贡献算重复

设这两个区间有相同的子串,则交集显然为RMQ(i+1,i+k),

在[i+1,i+k-1]里会被减掉一个不小于RMQ(i+1,i+k)的值,

同理[i+2,i+k]里也会被减掉一个不小于RMQ(i+1,i+k)的值

相同的都被减掉了,所以剩下的相邻的两个区间没有重复

传递性(因为贡献统计的是前缀,离得越远前缀越不同)

知任意两个长为k-1的区间都没有重复

 

对答案有贡献的区间长度不会小于k-1,

这说明统计贡献的时候只能在长度为k-1的区间里取

统计所有长度为k-1的区间的贡献即可

 

所有的并是全集,两两的交是空,显然是答案的一个划分

 

注意特判k==1的情形,

对于i开头的后缀,

有high[i]说明和上一个后缀有公共部分,

有high[i+1]说明下一个后缀和自己有公共部分

该后缀长度把k==2的情形减掉,就是答案

心得

我就是个字符串的辣鸡,什么都不会

以后见到不会的题及时补,别再死抠了

能看懂题解至上,别浪费不必要的时间

有SAM后缀自动机做法,回头再学吧

后续

2020.6.8回来再看这个题,之前自己写的什么垃圾玩意…

①对max的理解

考虑ab,abc,abcd,abcde,abcdef

其中[abc,abcde]出现了3次的前缀为abc,

而[ab,abcde]中出现4次的前缀为ab,[abc,abcdef]中出现4次的前缀为abc

这表明a ab abc都是出现超过3次的串,应该从abc里扣除,

即一个前缀只要在前后被覆盖了一次,它就应该被扣除

②对两个相邻的长度恰好为k的区间无交的理解

如果它们算入了相同的前缀,则这个前缀至少在相邻位置出现了k+1次,与之前被减过矛盾

代码

#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N = 1e5+10;
char s[N];
int sa[N], t[N], t2[N], c[N], n;
int Rank[N], high[N];
int dp[N][20]; 
ll ans;
void build(int m)
{
    int i, *x = t, *y = t2;
    for(i = 0; i < m; i++) c[i] = 0;
    for(i = 0; i < n; i++) c[x[i] = s[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(int k = 1, p; k <= n; k<<=1, m=p) {
        p = 0;
        for(i = n-k; i < n; i++) y[p++] = i;
        for(i = 0; i < n; i++) if(sa[i] >= k) y[p++] = sa[i] - k;
        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]] = (y[sa[i-1]] == y[sa[i]] && y[sa[i-1]+k] == y[sa[i]+k]) ? p-1 : p++;
        }
        if(p >= n) break;
    }
}
 
void get_high()
{
    int k = 0;
    for(int i = 1; i < n; i++) Rank[sa[i]] = i;
    for(int i = 0; i < n-1; i++) {
        if(k) k--;
        int j = sa[Rank[i]-1];
        while(s[i+k] == s[j+k]) k++;
        high[Rank[i]] = k;
        //if(Rank[i]==0)printf("...%d\n",i);
    }
}
void ST(int n,int high[])
{
	for(int i=1;i<n;++i)dp[i][0]=high[i];
	for(int j=1;(1<<j)<n;++j)
	{
		for(int i=1;i+(1<<j)-1<n;++i)
		{
			dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
		}
	} 
}
int RMQ(int l,int r)
{
	int k=log2(r-l+1);
	return min(dp[l][k],dp[r-(1<<k)+1][k]); 
}
void PR()
{
    printf("The rank is:\n");
    printf("%d", Rank[0]);
    //string p(s);
    for(int i = 1; i <= n-1; i++)
	{
	 printf(" %d", Rank[i]);
	 //cout<<p.substr(sa[i])<<endl;
    }
    puts("");
}
void init()
{
	memset(sa,0,sizeof(sa));
	memset(t,0,sizeof(t));
	memset(t2,0,sizeof(t2));
	memset(c,0,sizeof(c));
	memset(Rank,0,sizeof(Rank));
	memset(high,0,sizeof(high));
	memset(dp,INF,sizeof(dp));
    ans=0;
}
int T,k; 
int main()
{
	scanf("%d",&T);
	while(T--)
	{
	init();
	scanf("%d",&k); 
	scanf("%s", s);
	n = strlen(s) + 1;
	int maxi = 0;
	for(int i = 0; i < n; i++) {
		maxi = maxi > s[i] ? maxi : s[i];
	}
	s[n-1] = 0;
    build(maxi+1);
    get_high();
    //PR()
    /*后续操作*/
    high[n]=0;
    //for(int i=1;i<n;++i)
    //printf("%d:%d\n",i,high[i]);
    ST(n,high);
    for(int i=1;i+k-1<n;++i)
    ans+=max(0,(k==1?n-1-sa[i]:RMQ(i+1,i+k-1))-max(high[i],high[i+k]));
	printf("%lld\n",ans);
    } 
   	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值