【bzoj1396】 识别子串

http://www.lydsy.com/JudgeOnline/problem.php?id=1396 (题目链接)

题意

  问字符串S每一位的最短识别子串是多长(识别子串指包含这个字符且只出现在S中一次的子串)。

Solution

  很简单,搞出后缀数组以后,对于每一个后缀i,都可以求出从i向后延伸的最短识别子串,也就是${max(height[rank[i]],height[rank[i]+1])+1}$,注意一种情况,就是i与排在它相邻位置的后缀的lcp就等于它自己的长度,这种情况i是没有向后延伸的识别子串的。

  所以正着求一遍后缀数组,反着求一遍后缀数组,然后线段树区间修改每个后缀的最短识别子串覆盖范围内的点就可以了。

细节

  太久没写线段树,都忘记要开4倍空间了→_→

代码

// bzoj1396
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define LL long long
#define inf 2147483640
#define Pi acos(-1.0)
#define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
using namespace std;

const int maxn=100010;
struct Segtree {int l,r,s,tag;}tr[maxn<<2];
int sa[maxn],height[maxn],rank[maxn];
char s[maxn];

namespace Suffix {
	int wa[maxn],wb[maxn],ww[maxn];
	bool cmp(int *r,int a,int b,int l) {
		return r[a]==r[b] && r[a+l]==r[b+l];
	}
	void da(char *r,int *sa,int n,int m) {
		int i,j,p,*x=wa,*y=wb;
		for (i=0;i<=m;i++) ww[i]=0;
		for (i=1;i<=n;i++) ww[x[i]=r[i]]++;
		for (i=1;i<=m;i++) ww[i]+=ww[i-1];
		for (i=n;i>=1;i--) sa[ww[x[i]]--]=i;
		for (p=0,j=1;p<n;j*=2,m=p) {
			for (p=0,i=n-j+1;i<=n;i++) y[++p]=i;
			for (i=1;i<=n;i++) if (sa[i]>j) y[++p]=sa[i]-j;
			for (i=0;i<=m;i++) ww[i]=0;
			for (i=1;i<=n;i++) ww[x[y[i]]]++;
			for (i=1;i<=m;i++) ww[i]+=ww[i-1];
			for (i=n;i>=1;i--) sa[ww[x[y[i]]]--]=y[i];
			for (swap(x,y),p=x[sa[1]]=1,i=2;i<=n;i++)
				x[sa[i]]=cmp(y,sa[i-1],sa[i],j) ? p : ++p;
		}
	}
	void calheight(char *r,int *sa,int n) {
		for (int i=1;i<=n;i++) rank[sa[i]]=i;
		for (int k=0,i=1;i<=n;i++) {
			if (k) k--;
			int j=sa[rank[i]-1];
			while (r[i+k]==r[j+k]) k++;
			height[rank[i]]=k;
		}
	}
}

namespace SegTree {
	void build(int k,int s,int t) {
		tr[k].l=s,tr[k].r=t;
		if (s==t) {tr[k].s=inf;return;}
		int mid=(s+t)>>1;
		if (s<=mid) build(k<<1,s,mid);
		if (mid<t) build(k<<1|1,mid+1,t);
		tr[k].s=min(tr[k<<1].s,tr[k<<1|1].s);
	}
	void pushdown(int k) {
		int x=tr[k].tag;tr[k].tag=0;
		if (x<tr[k<<1].s) tr[k<<1].s=tr[k<<1].tag=x;
		if (x<tr[k<<1|1].s) tr[k<<1|1].s=tr[k<<1|1].tag=x;
	}
	void update(int k,int s,int t,int val) {
		int l=tr[k].l,r=tr[k].r,mid=(l+r)>>1;
		if (l==s && r==t) {if (tr[k].s>val) tr[k].s=tr[k].tag=val;return;}
		if (tr[k].tag) pushdown(k);
		if (t<=mid) update(k<<1,s,t,val);
		else if (s>mid) update(k<<1|1,s,t,val);
		else update(k<<1,s,mid,val),update(k<<1|1,mid+1,t,val);
		tr[k].s=max(tr[k<<1].s,tr[k<<1|1].s);
	}
	int query(int k,int p) {
		int l=tr[k].l,r=tr[k].r,mid=(l+r)>>1;
		if (l==r && l==p) return tr[k].s;
		if (p<=mid) return query(k<<1,p);
		else return query(k<<1|1,p);
	}
}

int main() {
	using namespace Suffix;
	using namespace SegTree;
	scanf("%s",s+1);
	int n=strlen(s+1);
	da(s,sa,n,300);
	calheight(s,sa,n);
	build(1,1,n);
	for (int i=1;i<=n;i++) {
		int val=max(height[rank[i]],height[rank[i]+1]);
		if (val==n-i+1) continue;
		update(1,i,i+val,val+1);
	}
	for (int i=1;i<=n/2;i++) swap(s[i],s[n-i+1]);
	da(s,sa,n,300);
	calheight(s,sa,n);
	for (int i=1;i<=n;i++) {
		int val=max(height[rank[i]],height[rank[i]+1]);
		if (val==n-i+1) continue;
		update(1,n-(i+val)+1,n-i+1,val+1);
	}
	for (int i=1;i<=n;i++) printf("%d\n",min(n,query(1,i)));
	return 0;
}
时间: 2024-10-25 17:07:23

【bzoj1396】 识别子串的相关文章

[BZOJ1396]识别子串 后缀自动机+线段树

1396: 识别子串 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 451  Solved: 290[Submit][Status][Discuss] Description Input 一行,一个由小写字母组成的字符串S,长度不超过10^5 Output L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长. Sample Input agoodcookcooksgoodfood Sample Output 1 2 3 3

bzoj1396 识别子串

建SAM,只有right集大小为1的节点对答案有贡献, 若其出现位置右端点为r,此节点可接受的最短串长为x,最长串长为y, 则对(r-x,r]用x更新最小值,对r-k (y<k≤x)则用k更新最小值 用两棵线段树维护答案,分别处理以上两种情况 #include<cstdio> #include<cstring> const int N=200005; int nx[N][26],fa[N],l[N],r[N],t[N],q[N],ql=0,qr=0,d[N],ptr=1,pv

【BZOJ1396】识别子串&amp;【BZOJ2865】字符串识别(后缀自动机)

[BZOJ1396]识别子串&[BZOJ2865]字符串识别(后缀自动机) 题面 自从有了DBZOJ 终于有地方交权限题了 题解 很明显,只出现了一次的串 在\(SAM\)的\(right/endpos\)集合大小一定为\(1\) 换句话说,在\(parent\)树上是叶子节点 找到所有这样的节点, 假设它的\(len=r\),它父亲的\(len=p\),它的结束位置为显然就是\(r\) 令\(l=r-p\) 以\(r\)结尾, 并且只出现了一次的串的左端点 为\(1..l\),那么,他们的答案

BZOJ1396&amp;2865 识别子串 【后缀自动机 + 线段树】

题目 输入格式 一行,一个由小写字母组成的字符串S,长度不超过10^5 输出格式 L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长. 输入样例 agoodcookcooksgoodfood 输出样例 1 2 3 3 2 2 3 3 2 2 3 3 2 1 2 3 3 2 1 2 3 4 题解 BZOJ AC200纪念,, 这两题题干是一样的,但唯一不同的是..后者卡空间[MLE得飞起] 先说解法: 我们知道后缀自动机上的parent树的每个节点子树中叶子的数量就是该节点

BZOJ 1396&amp;&amp;2865 识别子串[后缀自动机 线段树]

Description 在这个问题中,给定一个字符串S,与一个整数K,定义S的子串T=S(i, j)是关于第K位的识别子串,满足以下两个条件: 1.i≤K≤j. 2.子串T只在S中出现过一次. 例如,S="banana",K=5,则关于第K位的识别子串有"nana","anan","anana","nan","banan"和"banana". 现在,给定S,求对于S的

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

1396: 识别子串 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 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

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中

识别子串 (string)——后缀自动机+线段树

题目 [题目描述] 一般地,对于一个字符串 S,和 S 中第 $ i $ 个字符 x,定义子串 $ T=S(i.j) $ 为一个关于 x 的识别子申,当且仅当: 1.$ i \leq x \leq j $ 2.T 在 S 巾只出现一次 比如,对于 banana 的第 $ 5 $ 个字符,“nana”, “anan”,“anana”, “nan”,“banan” 和“banana”都是关于它的识别子串. 说你写一个程序,计算出对对于一个字符串 S,关于 S 的每一位的最短识别子串的长度. [输入格

bzoj 1396: 识别子串【SAM+线段树】

建个SAM,符合要求的串显然是|right|==1的节点多代表的串,设si[i]为right集合大小,p[i]为right最大的r点,这些都可以建出SAM后再parent树上求得 然后对弈si[i]==1的点,考虑它所代表的串是s(p[i]-dis[i]+1,p[i])~s(p[i]-dis[fa[i]],p[i]),然后对于p[i]-dis[i]+1<=x<=p[i]-dis[fa[i]],对x的答案的贡献是p[i]-x+1,带着-x不好做所以最后再-x,也就是贡献p[i]+1:对于p[i]