BZOJ 1396:识别子串 SA+树状数组+单调队列

1396: 识别子串

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 381  Solved: 243
[Submit][Status][Discuss]

Description

Input

一行,一个由小写字母组成的字符串S,长度不超过10^5

Output

L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长.

Sample Input

agoodcookcooksgoodfood

Sample Output

1

2

3

3

2

2

3

3

2

2

3

3

2

1

2

3

3

2

1

2

3

4

想法:

如果s[i..j]为识别串,s[i-y..j+x]同样也是识别串,可以通过枚举一个端点来得到最短识别串。

设wi=max{lcp(i,j)}+1=max{height[rank[i]],height[rank[i+1]]}+1

s[i..j]为识别子串<==>j-i+1>=wi。
即j>=i+max{height[rank[i]],height[rank[i+1]]},令hi=i+max{height[rank[i]],height[rank[i+1]]}

显然h(i)>h(i-1),ansi=min{max(j-i+1,hi-i+1)}i<=j

ansj=min(j-i+1)hi<=j;树状数组维护
ansj=min(hi-i+1)i<=j,hi>=j;线段树维护或者单调队列。
当 hx-x<hy-y&&x>y,y就无用了.

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
const int len(100000),limt(255);
int str[len+10],sfa[len+10],rank[len+10],height[len+10];
int tmp[len+10],p[len+10],cnt[len+10];
int n,c[len+10],ans[len+10],q[len+10],li,hi;char ch[len+10];
struct data{int x,y;}h[len+10];
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
bool com(int x,int y,int l){return (rank[x]==rank[y])&&(rank[x+l]==rank[y+l]);}
void doubling()
{
	for(int i=1;i<=n;i++){rank[i]=str[i];sfa[i]=i;}
	for(int pos=0,l=0,sigma=limt;pos<n;sigma=pos)
	{
		pos=0;
		for(int i=n-l+1;i<=n;i++)p[++pos]=i;
		for(int i=1;i<=n;i++)if(sfa[i]>l)p[++pos]=sfa[i]-l;
		memset(cnt,0,sizeof(int)*(sigma+1));pos=0;
		for(int i=1;i<=n;i++)cnt[rank[i]]++;
		for(int i=1;i<=sigma;i++)cnt[i]+=cnt[i-1];
		for(int i=n;i>=1;i--)sfa[cnt[rank[p[i]]]--]=p[i];
		for(int i=1;i<=n;i++)tmp[sfa[i]]=com(sfa[i],sfa[i-1],l)?pos:++pos;
		for(int i=1;i<=n;i++)rank[i]=tmp[i];
		l=!l?1:l<<1;
	}
	for(int i=1;i<=n;i++)rank[sfa[i]]=i;
	for(int i=1,k,j;i<=n;i++)
	{
		k=sfa[rank[i]-1];
		if(!k)continue;
		j=height[rank[i-1]];
		if(j)j--;
		while(str[i+j]==str[k+j])j++;
		height[rank[i]]=j;
	}
}
int query(int x){int sum=0;for(;x;x-=x&(-x))sum=max(sum,c[x]);return sum;}
void put(int x,int y){for(;x<=n;x+=x&(-x))c[x]=max(c[x],y);}
int main()
{
	freopen("C.in","r",stdin);
	freopen("C.out","w",stdout);
	scanf("%s",ch);n=strlen(ch);
	for(int i=1;i<=n;i++)str[i]=ch[i-1]-‘a‘+1;
	doubling();
	for(int i=1;i<=n;i++)
	{
		h[i].x=i+max(height[rank[i]],height[rank[i]+1]);
		h[i].y=i;
		if(h[i].x>n+1)printf("NO");
	}
	for(int i=1,t;i<=n;i++)
	{
		if(h[i].x!=n+1)ans[h[i].y]=h[i].x-h[i].y+1;
		else ans[h[i].y]=n;
		t=query(h[i].y);
		if(t)ans[h[i].y]=min(h[i].y-t+1,ans[h[i].y]);
		if(h[i].x!=n+1)put(h[i].x,h[i].y);
	}
	q[li=1]=1;hi=1;
	for(int i=2;i<=n;i++)
	{
		while(h[q[hi]].x<i&&hi<=li)hi++;
		if(hi<=li&&h[q[hi]].x>=i&&h[q[hi]].x!=n+1)ans[h[i].y]=min(ans[h[i].y],h[q[hi]].x-h[q[hi]].y+1);
		if(h[i].x!=n+1)
		{
			while(h[q[li]].x-h[q[li]].y>h[i].x-h[i].y&&li>=hi)li--;
			q[++li]=i;
		}
	}
	for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
	return 0;
}

  

时间: 2024-10-25 04:06:27

BZOJ 1396:识别子串 SA+树状数组+单调队列的相关文章

BZOJ 1396: 识别子串( 后缀数组 + 线段树 )

这道题各位大神好像都是用后缀自动机做的?.....蒟蒻就秀秀智商写一写后缀数组解法..... 求出Height数组后, 我们枚举每一位当做子串的开头. 如上图(x, y是height值), Heights数组中相邻的3个后缀, 假如我们枚举s2的第一个字符为开头, 那我们发现, 长度至少为len = max(x, y)+1, 才能满足题意(仅出现一次). 这个很好脑补...因为s2和其他串的LCP是RMQ, 肯定会<=LCP(s1,s2)或<=LCP(s2,s3). 然后就用len去更新s2中

[BZOJ 1901] Dynamic Rankings 【树状数组套线段树 || 线段树套线段树】

题目链接:BZOJ - 1901 题目分析 树状数组套线段树或线段树套线段树都可以解决这道题. 第一层是区间,第二层是权值. 空间复杂度和时间复杂度均为 O(n log n). 代码 树状数组套线段树 #include <iostream> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <cstring> usin

bzoj 2527 Meteors - 整体二分 - 树状数组

Description Byteotian Interstellar Union (BIU) has recently discovered a new planet in a nearby galaxy. The planet is unsuitable for colonisation due to strange meteor showers, which on the other hand make it an exceptionally interesting object of st

BZOJ 1103 大都市(dfs序+树状数组)

应该是一道很水的题吧... 显然可以用树链剖分解决这个问题,虽然不知道多一个log会不会T.但是由于问题的特殊性. 每次修改都是将边权为1的边修改为0,且询问的是点i到根节点的路径长度. 令点i到根节点的路径长度为w[i],显然初始时w[i]=dep[i].考虑修改边为(u,v),那么令u为深度大的点. 那么u的子树的所有答案就要减1.考虑dfs序,则每次需要修改的是一段连续的区间. 树状数组维护单点查询,区间修改,美滋滋. # include <cstdio> # include <c

BZOJ 3211 花神游历各国 树状数组(线段树)+优化

题意:给你一段区间,然后每个点的初始值都告诉你,现有两种操作,一种是给你一个小区间的左右端点,之后把这个区间内的所有值都开根号,另一种就是区间求值. 方法:树状数组维护求和,巧妙开根号.(线段树) 解析:这道是某次考试考的题- -.当时也没想到快的开根号方法,暴力开根号好像70分吧. 首先要明确一个事情:被开根号的数最大是109,而109开几次会开到1呢?用计算器算一下发现5次就将这个最大的数开到1了,而1开根号怎么开都是1,所以如果再对其进行开根号操作,仅仅是无用的浪费时间了.所以怎么维护这种

[BZOJ 3196] 二逼平衡树 树状数组套主席树

3196: Tyvj 1730 二逼平衡树 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 3357  Solved: 1326[Submit][Status][Discuss] Description 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:1.查询k在区间内的排名2.查询区间内排名为k的值3.修改某一位值上的数值4.查询k在区间内的前驱(前驱定义为小于x,且最大的数)5.查询k在区间内的后继(后继定义为

【BZOJ】1452: [JSOI2009]Count 树状数组

Description Input Output Sample Input Sample Output 1 2 HINT 题解: 二维的树状数组啊+一维的颜色状态,然后直接做就好……实际上比照一维的树状数组就是多了一个for循环,然后查询操作的时候就相当于查询某一矩阵的大小,树状数组起到一个类似前缀和的作用.   1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <

BZOJ 3211 花神游历各国 树状数组+并查集

题目大意:花神对每一个国家有一个喜爱程度,有的时候他会对连续的一段国家进行访问,求他的喜爱程度的和:有的时候他会对连续的一段国家产生厌恶,喜爱程度变成sqrt(x)下取整. 思路:乍一看好像是RMQ问题,用线段树就可以水过,但是开根号的标记怎么下传?这是一个严重的问题,所以我们要换一个思路. 注意到开根号有一个有趣的性质:sqrt(1) = 1,sqrt(0) = 0,而且所有的数字经过有限次的开根号运算都会变成1.这个性质就很好了.我们对每一个点暴力开根号,然后当这个店的点权变成1的时候就打一

BZOJ 2762 JLOI2011 不等式组 树状数组

题目大意:给定一些形如ax+b>c的不等式,支持插入和修改,以及询问当x=k时有多少不等式成立 将不等式变形 可以得到每个不等式成立时x的取值范围 在树状数组上统计即可 注意事项: 1.a可以等于0 此时若b>c x∈R 若b<=c x∈? 2.x的取值范围可能超过[-1000000,1000000] 3.由于有负数 所以区间修改时左右端点都要加上1000001 若加上1000000则死循环 4.小于不是小于等于 注意整除是的讨论 我的做法是x>y大于就是x>=floor(y