【BZOJ-2555】SubString 后缀自动机 + LinkCutTree

2555: SubString

Time Limit: 30 Sec  Memory Limit: 512 MB
Submit: 1936  Solved: 551
[Submit][Status][Discuss]

Description

懒得写背景了,给你一个字符串init,要求你支持两个操作
    (1):在当前字符串的后面插入一个字符串
    (2):询问字符串s在当前字符串中出现了几次?(作为连续子串)
    你必须在线支持这些操作。

Input

第一行一个数Q表示操作个数
    第二行一个字符串表示初始字符串init
    接下来Q行,每行2个字符串Type,Str 
    Type是ADD的话表示在后面插入字符串。
    Type是QUERY的话表示询问某字符串在当前字符串中出现了几次。
    为了体现在线操作,你需要维护一个变量mask,初始值为0
    
    读入串Str之后,使用这个过程将之解码成真正询问的串TrueStr。
    询问的时候,对TrueStr询问后输出一行答案Result
    然后mask = mask xor Result  
    插入的时候,将TrueStr插到当前字符串后面即可。

HINT:ADD和QUERY操作的字符串都需要解压

Output

Sample Input

2
A
QUERY B
ADD BBABBBBAAB

Sample Output

0

HINT

40 % 的数据字符串最终长度 <= 20000,询问次数<= 1000,询问总长度<= 10000
100 % 的数据字符串最终长度 <= 600000,询问次数<= 10000,询问总长度<= 3000000

新加数据一组--2015.05.20

Source

Ctsc模拟赛By 洁妹

Solution

论文里最难搞的一道题,谢谢 abclzr队长 的帮助。

一个串在模板串中的出现次数显然就是$|Right(s)|$,然后这个的求法是$Parent$树的子树中的叶子节点个数。

暴力的查询是单次$O(N)$,总体$O(N^{2})$的,所以要利用数据结构LCT维护,实现查询$O(logN)$。

在构建SAM的同时要在LCT上进行相应的Link/Cut操作,在Cut的时候,需要将其贡献减去。

样例有点弱,自己搞了个测试点:

5
ABABA

QUERY AB
QUERY A
ADD BAB
QUERY AB
QUERY A

Input

2
3
3
4

Output

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 1200010
char S[MAXN];
int N,Q,M,ans,Mask;
inline void read()
{
    string str=S+1;
    int mask=Mask;
    for (int i=0; i<str.length(); i++)
        {
            mask=(mask*131+i)%str.length();
            swap(str[i],str[mask]);
        }
    for (int i=0,tot=0; i<str.length(); i++) S[++tot]=str[i];
}
namespace LCT
{
    int fa[MAXN],ch[MAXN][2],val[MAXN],tag[MAXN];
    inline bool is_root(int x) {return !fa[x] || ch[fa[x]][1]!=x&&ch[fa[x]][0]!=x;}
    inline void Add(int x,int v) {if (!x) return; val[x]+=v,tag[x]+=v;}
    inline void Pushdown(int x) {if (tag[x]) Add(ch[x][0],tag[x]),Add(ch[x][1],tag[x]),tag[x]=0;}
    inline void Rotate(int x)
    {
        int y=fa[x],w=ch[y][1]==x,z=fa[y];
        ch[y][w]=ch[x][w^1];
        if (ch[x][w^1]) fa[ch[x][w^1]]=y;
        if (ch[z][0]==y) ch[z][0]=x; else if (ch[z][1]==y) ch[z][1]=x;
        fa[x]=fa[y]; fa[y]=x; ch[x][w^1]=y;
    }
    int stack[MAXN];
    inline void Splay(int x)
    {
        int top=0,t=x,y; stack[++top]=x;
        while (!is_root(t)) stack[++top]=t=fa[t];
        while (top) Pushdown(stack[top--]);
        while (!is_root(x))
            {
                y=fa[x];
                if (!is_root(y))
                    if ((ch[fa[y]][0]==y)^(ch[y][0]==x)) Rotate(x);
                        else Rotate(y);
                Rotate(x);
            }
    }
    inline void Access(int x) {for (int y=0; x; y=x,x=fa[x]) Splay(x),ch[x][1]=y;}
    inline void Link(int x,int y) {fa[x]=y; Access(y); Splay(y); Add(y,val[x]);}
    inline void Cut(int x) {Access(x); Splay(x); Add(ch[x][0],-val[x]); fa[ch[x][0]]=0,ch[x][0]=0;}
}using namespace LCT;
namespace SAM
{
    int son[MAXN][27],len[MAXN],par[MAXN];
    int root,last,sz;
    inline void Init() {root=last=sz=1;}
    inline void Extend(int c)
    {
        int cur=++sz,p=last;
        len[cur]=len[p]+1; LCT::val[cur]=1;
        while (p && !son[p][c]) son[p][c]=cur,p=par[p];
        if (!p) par[cur]=root,LCT::Link(cur,root);
        else
            {
                int q=son[p][c];
                if (len[p]+1==len[q]) par[cur]=q,LCT::Link(cur,q);
                else
                    {
                        int nq=++sz;
                        memcpy(son[nq],son[q],sizeof(son[nq]));
                        len[nq]=len[p]+1,par[nq]=par[q]; LCT::Link(nq,par[nq]);
                        while (p && son[p][c]==q) son[p][c]=nq,p=par[p];
                        par[cur]=par[q]=nq;
                        LCT::Cut(q); LCT::Link(cur,nq); LCT::Link(q,nq);
                    }
            }
        last=cur;
    }
    inline void Build() {Init(); for (int i=1; i<=N; i++) Extend(S[i]-‘A‘+1);}
    inline void Insert()
    {
        read(); M=strlen(S+1);
        for (int i=1; i<=M; i++) Extend(S[i]-‘A‘+1);
    }
    inline int Query()
    {
        read(); M=strlen(S+1);
        int now=root;
        for (int i=1; i<=M; i++)
            if (!son[now][S[i]-‘A‘+1]) return 0;
                else now=son[now][S[i]-‘A‘+1];
        LCT::Splay(now);
        return val[now];
    }
}using namespace SAM;
int main()
{
    scanf("%d",&Q);
    scanf("%s",S+1); N=strlen(S+1);
    SAM::Build();
    while (Q--)
        {
            char opt[10];
            scanf("%s%s",opt+1,S+1);
            switch (opt[1])
                {
                    case ‘A‘ : SAM::Insert(); break;
                    case ‘Q‘ : printf("%d\n",ans=SAM::Query()); Mask^=ans; break;
                }
//            for (int i=1; i<=sz; i++) printf("%d %d %d %d\n",i,ch[i][0],ch[i][1],val[i]);
        }
    return 0;
}

太久没看LCT了,出现大片遗忘,背了个模板都能弄错,其实应该先复习一下LCT再写这道题的。

时间: 2024-12-29 21:23:31

【BZOJ-2555】SubString 后缀自动机 + LinkCutTree的相关文章

BZOJ 2555 Substring 后缀自动机+Link-Cut-Tree

题目大意:给定一个初始字符串,提供两种操作: 1.在这个字符串的后面连接一个字符串 2.询问某个字符串在当前串中出现了多少次 SAM大叔的自动机~~ 对于每个询问就是在后缀自动机上找到该子串所对应的节点 找不到返回0 然后这个节点的Right集合的大小就是这个子串的出现次数 每次Extend的时候将新建节点沿着parent指针到根的路径上所有点的Right集合大小+1即可 分裂节点的时候要将Right集合一并复制 这方法虽然暴力但是非常有效 由于parent是一棵树,所以可以用LCT来维护这棵树

bzoj 2555 SubString —— 后缀自动机+LCT

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2555 建立后缀自动机,就可以直接加入新串了: 出现次数就是 Right 集合的大小,需要查询 Parent 树上的子树和: 所以可以用 LCT 维护 Parent 树,因为 Parent 树是有根树所以不需要 makeroot: 代码中的两种 cut 写法都可以,其实这里的 splay 节点上记的 siz 值不是 splay 子树里的而是原子树( Parent 树上)里的: 注意读入的函数

BZOJ 2555 SubString 后缀自动机

题目大意:给出一个字符串,支持在线在字符串后面加一个字符串,查询一个字符串在串中出现过几次. 思路:如果不想写正解的话,这个题就是后缀自动机的简单应用.正解其实是LCT+SAM,但是时间比暴力慢一倍... 暴力就很简单了,正序建立后缀自动机,每次查询的时候找到位置直接输出size的值.注意两点,一个是分裂节点的时候,size也要复制过去.查询的时候发现找不到要return 0; CODE: #include <cstdio> #include <cstring> #include

BZOJ 2555: SubString [后缀自动机 LCT]

2555: SubString Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 2045  Solved: 583[Submit][Status][Discuss] Description 懒得写背景了,给你一个字符串init,要求你支持两个操作        (1):在当前字符串的后面插入一个字符串        (2):询问字符串s在当前字符串中出现了几次?(作为连续子串)        你必须在线支持这些操作. Input 第一行一个数Q表示

bzoj 2555 SubString——后缀自动机+LCT

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2555 要维护 right 集合的大小.因为 fa 会变,且 fa 构成一棵树,所以考虑用 LCT 来维护-- 和平常写的 LCT 不太一样.因为要的值是原树上子树里的值,所以没有 makeroot ,splay 里不维护 splay 里的子树信息,只维护加法标记,表示 link 一下就给原树的自己到根的那条链上的所有点加了自己的值.cut 就是减掉自己的值.所以 query 或者 spla

2555: SubString 后缀自动机+LCT

2555: SubString Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 688  Solved: 235[Submit][Status][Discuss] Description 懒得写背景了,给你一个字符串init,要求你支持两个操作          (1):在当前字符串的后面插入一个字符串          (2):询问字符串s在当前字符串中出现了几次?(作为连续子串)          你必须在线支持这些操作. Input 第一行一

字符串(LCT,后缀自动机):BZOJ 2555 SubString

2555: SubString Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 1620  Solved: 471 Description 懒得写背景了,给你一个字符串init,要求你支持两个操作          (1):在当前字符串的后面插入一个字符串          (2):询问字符串s在当前字符串中出现了几次?(作为连续子串)          你必须在线支持这些操作. Input 第一行一个数Q表示操作个数          第二行一个

●BZOJ 2555 SubString

题链: http://www.lydsy.com/JudgeOnline/problem.php?id=2555题解: 后缀自动机+LCT 不难发现,对于输入的询问串,在自动机里trans后的到的状态的Right集合的大小就是答案. 那么后缀自动机本身就是支持在线添加的,问题就是如何维护好parent树,即如何维护好每个状态的Right集合. 那么Link-Cut-Tree就显然可以完成动态维护parent树的任务. (这里是维护的一颗根固定的树,没有Beroot()等换根函数) 代码: #in

spoj 1811 LCS - Longest Common Substring (后缀自动机)

spoj 1811 LCS - Longest Common Substring 题意: 给出两个串S, T, 求最长公共子串. 限制: |S|, |T| <= 1e5 思路: dp O(n^2) 铁定超时 后缀数组 O(nlog(n)) 在spoj上没试过,感觉也会被卡掉 后缀自动机 O(n) 我们考虑用SAM读入字符串B; 令当前状态为s,同时最大匹配长度为len; 我们读入字符x.如果s有标号为x的边,那么s=trans(s,x),len = len+1; 否则我们找到s的第一个祖先a,它