BZOJ1396&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树的每个节点子树中叶子的数量就是该节点表示的串的出现次数

显然我们要找的是子树叶子数为1,即代表出现次数为1的节点

parent树中的节点,要么是叶子节点,要么有至少两个儿子

由上面这个性质我们可以知道满足条件的只有叶子结点

所以我们连序都不用排了,直接统计不被父亲指针指向的节点

对于节点i,其表示的串为长度为\([step[pre[i]] + 1,step[i]]\)的串【pre为parent树父亲节点】

由于该串是唯一的,所以该串表示的最小串为后缀的子串都是唯一的

我们令其最小串为[l,r],其中\(l = step[i] - step[pre[i]],r = step[i]\)

我们分为两类:

①被最小串包含的位置[l,r],更新其最短识别长度为\(r - l + 1\)

②超出最小串的位置[1,l-1],更新其最短识别长度为\(r - i + 1\)【i为字符位置】

那么我们可以开两颗线段树分别维护①和②的最小值,最后输出时二者取最小即可【维护②的先不减i,输出时再减】

什么意思?每个位置会被两种形式的长度更新,①是一个值,②是一个值还要减去自身的位置,②不好处理,我们分开保存

T1就搞完了~~

等等,T2呢?

原来是数据范围变大了,改大,一交,雾草。。MLE

= =

何破?

①改用后缀数组【还没写】

②将ch改为map保存【好吧我就是这样A的】

T2太丢脸,贴T1代码吧,,

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long int
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<‘ ‘; puts("");
#define ls (u << 1)
#define rs (u << 1 | 1)
using namespace std;
const int maxn = 200005,maxm = 400005,INF = 1000000000;
inline int read(){
    int out = 0,flag = 1; char c = getchar();
    while (c < 48 || c > 57) {if (c == ‘-‘) flag = -1; c = getchar();}
    while (c >= 48 && c <= 57) {out = (out << 3) + (out << 1) + c - ‘0‘; c = getchar();}
    return out * flag;
}
int pre[maxn],ch[maxn][26],step[maxn],val[maxn],cnt,last,n;
char s[maxn];
void ins(int x){
    int p = last,np = ++cnt;
    last = np; step[np] = step[p] + 1;
    while (p && !ch[p][x]) ch[p][x] = np,p = pre[p];
    if (!p) pre[np] = 1;
    else {
        int q = ch[p][x];
        if (step[q] == step[p] + 1) pre[np] = q;
        else {
            int nq = ++cnt; step[nq] = step[p] + 1;
            memcpy(ch[nq],ch[q],sizeof(ch[q]));
            pre[nq] = pre[q]; pre[q] = pre[np] = nq;
            while (ch[p][x] == q) ch[p][x] = nq,p = pre[p];
        }
    }
}
struct Seg{
    int mn[2 * maxn],tag[2 * maxn];
    Seg(){for (int i = 0; i < maxm; i++) mn[i] = tag[i] = INF;}
    void pd(int u){
        if (tag[u] != INF){
            mn[ls] = min(mn[ls],tag[u]); tag[ls] = min(tag[ls],tag[u]);
            mn[rs] = min(mn[rs],tag[u]); tag[rs] = min(tag[rs],tag[u]);
            tag[u] = INF;
        }
    }
    void modify(int u,int l,int r,int L,int R,int v){
        if (L > R) return;
        if (l >= L && r <= R){
            mn[u] = min(mn[u],v);
            tag[u] = min(tag[u],v);
            return;
        }
        pd(u);
        int mid = l + r >> 1;
        if (mid >= L) modify(ls,l,mid,L,R,v);
        if (mid < R) modify(rs,mid + 1,r,L,R,v);
        mn[u] = min(mn[ls],mn[rs]);
    }
    int query(int u,int l,int r,int pos){
        if (l == r) return mn[u];
        pd(u);
        int mid = l + r >> 1;
        if (mid >= pos) return query(ls,l,mid,pos);
        else return query(rs,mid + 1,r,pos);
    }
}A,B;
void solve(){
    REP(i,cnt) val[i] = 1;
    REP(i,cnt) val[pre[i]] = 0;
    REP(i,cnt) if (val[i]){
        int l = step[i] - step[pre[i]],r = step[i];
        A.modify(1,1,n,l,r,r - l + 1);
        B.modify(1,1,n,1,l - 1,r + 1);
    }
    REP(i,n)
        printf("%d\n",min(A.query(1,1,n,i),B.query(1,1,n,i) - i));
}
int main(){
    //freopen("in.txt","r",stdin);
    //freopen("out1.txt","w",stdout);
    scanf("%s",s + 1);
    n = strlen(s + 1); cnt = last = 1;
    REP(i,n) ins(s[i] - ‘a‘);
    solve();
    return 0;
}

原文地址:https://www.cnblogs.com/Mychael/p/8311818.html

时间: 2024-11-04 22:15:59

BZOJ1396&2865 识别子串 【后缀自动机 + 线段树】的相关文章

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的

[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: 识别子串( 后缀数组 + 线段树 )

这道题各位大神好像都是用后缀自动机做的?.....蒟蒻就秀秀智商写一写后缀数组解法..... 求出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 的每一位的最短识别子串的长度. [输入格

CF666E Forensic Examination(后缀自动机+线段树合并)

给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[pl..pr]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 我们首先对m个字符串数组建出后缀自动机,然后我们可以通过跳trans边找到S前i个字符代表的前缀的最长后缀.我们要找的是S[pl..pr]并不是以pr结束最长的后缀,但我们可以确定S[pl..pr]一定是当前点的祖先所以当我们跳到pr代表的点时我们倍增往上跳知道找到一个点的长度刚好大于等于pr-pl+1,这个点就是询问

HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)

题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点,求第k大的下标即可. 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10,mod=998244353; 5 char buf[N]; 6 int s[N],sa[

CF700E:Cool Slogans(后缀自动机,线段树合并)

Description 给你一个字符串,如果一个串包含两个不重叠的相同子串,那么这个串的价值就是子串的价值+1.问你给定字符串的最大价值子串的价值. Input 第一行读入字符串长度$n$,第二行是字符串. Output 一行答案. Sample Input1 3abc Sample Output1 1 Sample Input2 5ddddd Sample Output2 5 Sample Input3 11abracadabra Sample Output3 3 Solution 首先把后缀

Cool Slogans(后缀自动机+线段树+dp)

传送门:https://www.luogu.org/problemnew/show/CF700 先手动模拟一下: 原串:abracadabra s数组依次是:abracadabra,abra,a 可以发现,每一步我们找最长的在上一个串中出现两次的子串,即可得到最优解 很容易想到dp: 定义两个数组: dp[i]:使用节点i最长的那个字符串的答案mx[i]:节点i最长的那个字符串对应的节点 设A是B的子串 if(A在B中出现两次) dp[B]=dp[A]+1,mx[B]=B;else dp[B]=

CF.666E.Forensic Examination(广义后缀自动机 线段树合并)

题目链接 \(Description\) 给定串S和m个串Ti.Q次询问,每次询问l,r,pl,pr,求S[pl~pr]在Tl~Tr中的哪个串出现次数最多,输出最多次数及其T的下标.若有多个,输出下标最小的. \(Solution\) 挺好的题吧 对T个串建SAM,然后要求出SAM每个节点上|right|最大的是哪个串. 每个节点的|right|可以在DFS parent树时合并子节点得到,如果用线段树维护,|right|最大的位置也可以合并得到. 这样可以离线处理询问,最后DFS一遍得到答案.