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中前len个字符的答案, 线段树维护. 然后对于长度lth>len的也肯定是合法的, 他们对s2的前lth个字符都有贡献...但是事实上lth对前lth-1个字符c的贡献是没有卵用的....(因为小于同样字符开头的以c结尾的串的贡献或者是len的贡献), 所以lth>=len对第lth个字符有贡献.

容易看出这样的贡献是成等差数列的。。。。线段树维护就OK了.

时间复杂度O(N log N), 空间复杂度O(N)

-------------------------------------------------------------------------

#include<cstdio>

#include<cstring>

#include<algorithm>

using namespace std;

const int maxn = 100009;

char S[maxn];

int N, L, R, Val;

int Height[maxn], Rank[maxn], Sa[maxn], cnt[maxn];

inline void Min(int &x, int t) {

if(t < x) x = t;

}

inline void Max(int &x, int t) {

if(t > x) x = t;

}

void BuildSA(int m) {

int *x = Height, *y = Rank;

for(int i = 0; i < m; i++) cnt[i] = 0;

for(int i = 0; i < N; i++) cnt[x[i] = S[i]]++;

for(int i = 1; i < m; i++) cnt[i] += cnt[i - 1];

for(int i = N; i--; ) Sa[--cnt[x[i]]] = i;

for(int k = 1, p = 0; k <= N; k <<= 1, 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++) cnt[i] = 0;

for(int i = 0; i < N; i++) cnt[x[y[i]]]++;

for(int i = 1; i < m; i++) cnt[i] += cnt[i - 1];

for(int i = N; i--; ) Sa[--cnt[x[y[i]]]] = y[i];

swap(x, y);

p = (x[Sa[0]] = 0) + 1;

for(int i = 1; i < N; i++) {

if(y[Sa[i]] != y[Sa[i - 1]] || y[Sa[i] + k] != y[Sa[i - 1] + k]) p++;

x[Sa[i]] = p - 1;

}

if((m = p) >= N) break;

}

for(int i = 0; i < N; i++) Rank[Sa[i]] = i;

Height[0] = 0;

for(int i = 0, h = 0; i < N; i++) if(Rank[i]) {

if(h) h--;

while(S[i + h] == S[Sa[Rank[i] - 1] + h]) h++;

Height[Rank[i]] = h;

}

}

struct Node {

Node *lc, *rc;

int n, d;

inline void pd(int len) {

if(n != maxn) {

Min(lc->n, n);

Min(rc->n, n);

}

if(d != maxn) {

Min(lc->d, d);

Min(rc->d, d + ((len + 1) >> 1));

}

}

} pool[maxn << 1], *pt = pool, *Root;

void Build(Node* t, int l, int r) {

t->n = t->d = maxn;

if(l != r) {

int m = (l + r) >> 1;

Build(t->lc = pt++, l, m);

Build(t->rc = pt++, m + 1, r);

}

}

void Modify(Node* t, int l, int r) {

if(L <= l && r <= R) {

Min(t->n, Val);

} else {

int m = (l + r) >> 1;

if(L <= m) Modify(t->lc, l, m);

if(m < R) Modify(t->rc, m + 1, r);

}

}

void Change(Node* t, int l, int r) {

if(L <= l && r <= R) {

Min(t->d, Val + l - L);

} else {

int m = (l + r) >> 1;

if(L <= m) Change(t->lc, l, m);

if(m < R) Change(t->rc, m + 1, r);

}

}

void DFS(Node* t, int l, int r) {

if(l != r) {

int m = (l + r) >> 1;

t->pd(r - l + 1);

DFS(t->lc, l, m);

DFS(t->rc, m + 1, r);

} else

printf("%d\n", min(t->d, t->n));

}

int main() {

scanf("%s", S);

N = strlen(S);

S[N++] = ‘$‘;

BuildSA(‘z‘ + 1);

int n = N - 1;

Build(Root = pt++, 1, n);

Height[N] = 0;

for(int i = 1; i < N; i++) {

Val = max(Height[i], Height[i + 1]) + 1;

if(Val > 1) {

if(Sa[i] + Val > n) continue;

L = Sa[i] + 1, R = L + Val - 2;

Modify(Root, 1, n);

}

L = Sa[i] + Val, R = n;

if(L > R) continue;

Change(Root, 1, n);

}

DFS(Root, 1, n);

return 0;

}

-------------------------------------------------------------------------

1396: 识别子串

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 201  Solved: 119
[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

HINT

Source

时间: 2024-10-11 18:00:04

BZOJ 1396: 识别子串( 后缀数组 + 线段树 )的相关文章

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]

[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

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 3230 相似子串 | 后缀数组 二分 ST表

BZOJ 3230 相似子串 题面 题解 首先我们要知道询问的两个子串的位置. 先正常跑一遍后缀数组并求出height数组. 对于每一个后缀suffix(i),考虑以i开头的子串有多少是之前没有出现过的,也就是考虑左端点在i.右端点在什么范围内时这个子串没有出现过--答案是右端点在[i + height[i] - 1, n]范围内时这个子串没出现过,即右端点在没有被"i与排在前一个的后缀的公共前缀"覆盖的部分时,这个子串没有出现过. 那么我们记录以每个i开头的新子串的数量,求前缀和,然

【XSY1551】往事 广义后缀数组 线段树合并

题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\)最长公共前缀,\(LCS=\)最长公共后缀 \(1\leq n\leq 200000\),字符集为\(\{0\ldots 300\}\) 题解 我们先看看这个\(LCP(s_u,s_v)\)怎么求 广义后缀自动机不行.广义后缀树可能可以,但我不会.广义后缀数组可以.然后我就开始手推广义后缀数组 广义

bzoj 3230 相似子串——后缀数组

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3230 作出后缀数组,从 LCP 看每个位置对于本质不同子串的贡献,而且他们已经按前面部分排好序了,所以直接在 sa[ ] 上二分就能找到询问的子串. 找最长公共前缀就用 ht[ ] 和子串的长度比较就行.找最长公共后缀就一开始把原串翻转,做出翻转后的 ht[ ] ,就能查询了. 也可以二分一个最长公共后缀的位置,然后用正常的 ht[ ] 判断. 注意 long long . #includ

hdu 1166 树状数组 线段树

敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 51177    Accepted Submission(s): 21427 Problem Description C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务

poj 1743 最长不重叠重复子串 后缀数组+lcp+二分

题比较容易读懂,但是建模需动点脑子: 一个子串加常数形成的子串认为跟子串相同,求最长不重叠重复子串 题目中说 is disjoint from (i.e., non-overlapping with) at least one of its other appearance(s) 意味着不能重叠,举个例子 1, 2,3,  52, 53,54 1,2, 3和 52, 53,54满足题意,差值为51 枚举差值肯定不行------看了题解明白的:: 后项减去前一项得到: 1,1,1,49,1,1