BZOJ4556:[Tjoi2016&Heoi2016]字符串 (后缀自动机+树上倍增+二分答案+线段树合并)

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4556


题目分析:我发现我对线段树合并一无所知QAQ。

先讲一种简单的做法:我们可以将后缀数组建出来,对于每个询问二分一个答案mid。然后从Rank[c]往上下两个方向跳,找到一个区间[L,R],使得这个区间的后缀和c开头的后缀的LCP大于等于mid。那么如果sa[L]~sa[R]中有落在[a,b-mid+1]的数,最终答案就大于等于mid。要判断区间[L,R]中是否有属于某个权值区域的数,写个可持久化的权值线段树,然后差分一下就行。

上面的做法代码很短,但常数偏大。其实还可以把反串的后缀自动机建出来,二分完答案mid后,用树上倍增跳到Right(c)-mid所对应的节点,然后看这个节点的Right集中是否包含[b+mid-1,a]中的数(上文的a,b,c均指在反串中的位置)。这可以用线段树合并来实现(当然也可以用DFS序+可持久化线段树,不过我做这题的主要目的是写一写线段树合并)。

为了在省选的赛场上也能码出这种超级数据结构题,我特地测试了一下自己写这题代码的时间。结果我用75min写好了代码,交上去WA了一次,然后用一组手造的小数据检查了一遍,发现有个n+1写成了n,还有线段树合并的时候忘了新建节点qaq,然后再交了一次就……

就不停地RE!!!我不停地debug还是没有发现任何错。到了第二天中午(也就是今天中午),我迫不得已把网上某位dalao的程序copy下来对拍,还是没有拍出任何错QAQ。于是我只好将代码一段一段注释掉,交上BZOJ,看看是哪里RE了。结果我发现是线段树合并出错了。

假设线段树的叶子节点有N个,那么线段树合并的总空间应该是 2Nlog(N) (至少我写的是这样),而我只开了 Nlog(N) 。这个应该比较好证明:考虑权值线段树上某个代表区间[L,R]的节点,一开始每一个数都要单独开一条链,于是这个位置的节点被新建了R-L+1次。然后区间里的每两个数合并,都要新开一次该位置的节点,所以又要最多开R-L次,于是所有节点加起来就是 2Nlog(N) 的空间。

那为什么对拍不出错呢?因为我的字符串是随机生成的,导致建出来的SAM状态数很少。而线段树的叶子节点个数等于SAM的状态数,所以不会爆。我将空间改成 2Nlog(N) 之后MLE了,于是我稍微调小了点空间,终于过了。然后我就对着这道题花掉了75min写代码+一个中午debug的时间……


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=100100;
const int maxm=7000000;
const int maxl=20;
const int maxc=26;

struct Seg
{
    int sum;
    Seg *lson,*rson;
} tree[maxm];
int Tcur=-1;

struct Tnode;

struct edge
{
    Tnode *obj;
    edge *Next;
} e[maxn<<1];
int Ecur=-1;

struct Tnode
{
    int val,Right;
    edge *head;
    Seg *Seg_Root;
    Tnode *son[maxc],*Fa[maxl],*parent;
} SAM[maxn<<1];
Tnode *Last[maxn];
Tnode *Root;
int cur=-1;

int a[maxn];
char s[maxn];
int n,m;
int A,b,c,d;

Seg *New_Seg()
{
    Tcur++;
    tree[Tcur].sum=0;
    tree[Tcur].lson=tree[Tcur].rson=tree;
    return tree+Tcur;
}

Tnode *New_node(int v,int c)
{
    cur++;
    SAM[cur].val=v;
    SAM[cur].Right=c;
    SAM[cur].parent=NULL;
    SAM[cur].head=NULL;
    SAM[cur].Seg_Root=tree;
    for (int i=0; i<maxc; i++) SAM[cur].son[i]=NULL;
    for (int i=0; i<maxl; i++) SAM[cur].Fa[i]=NULL;
    return SAM+cur;
}

void Add(Tnode *x,Tnode *y)
{
    Ecur++;
    e[Ecur].obj=y;
    e[Ecur].Next=x->head;
    x->head=e+Ecur;
}

void Update(Seg *&root,int L,int R,int x)
{
    if (root==tree) root=New_Seg();
    if (L==R) root->sum=1;
    else
    {
        int mid=(L+R)>>1;
        if (x<=mid) Update(root->lson,L,mid,x);
        else Update(root->rson,mid+1,R,x);
        root->sum=root->lson->sum+root->rson->sum;
    }
}

void Merge(Seg *&x,Seg *y)
{
    if (y==tree) return;
    if (x==tree)
    {
        x=y;
        return;
    }

    Seg *z=x;
    x=New_Seg();
    (*x)=(*z); //要记得新开节点!!!
    Merge(x->lson,y->lson);
    Merge(x->rson,y->rson);
    x->sum=x->lson->sum+x->rson->sum;
}

void Dfs(Tnode *node)
{
    Update(node->Seg_Root,1,n+1,node->Right);
    for (edge *p=node->head; p; p=p->Next)
    {
        Tnode *to=p->obj;
        Dfs(to);
        Merge(node->Seg_Root,to->Seg_Root);
//我这里的线段树合并要两倍SAM节点数*log(n)的空间,也就是maxn*maxl<<2(注意被卡空间)!!!
    }
}

void Build_SAM()
{
    Root=New_node(0,n+1);
    Last[0]=Root;
    for (int i=1; i<=n; i++)
    {
        Tnode *P=Last[i-1],*NP=New_node(i,i);
        Last[i]=NP;
        int to=a[i];
        while ( P && !P->son[to] ) P->son[to]=NP,P=P->parent;
        if (!P) NP->parent=Root;
        else
        {
            Tnode *Q=P->son[to];
            if (P->val+1==Q->val) NP->parent=Q;
            else
            {
                Tnode *NQ=New_node(P->val+1,n+1);
                for (int i=0; i<maxc; i++) NQ->son[i]=Q->son[i];
                NQ->parent=Q->parent;
                NP->parent=Q->parent=NQ;
                while ( P && P->son[to]==Q ) P->son[to]=NQ,P=P->parent;
            }
        }
    }

    for (int i=0; i<=cur; i++)
    {
        Tnode *P=SAM+i;
        P->Fa[0]=P->parent;
    }
    for (int j=1; j<maxl; j++)
        for (int i=0; i<=cur; i++)
        {
            Tnode *P=SAM+i;
            if (P->Fa[j-1]) P->Fa[j]=P->Fa[j-1]->Fa[j-1];
        }

    for (int i=1; i<=cur; i++)
    {
        Tnode *P=SAM+i;
        Add(P->parent,P);
    }
    Dfs(Root);
}

Tnode *Jump(int x,int y)
{
    Tnode *P=Last[x];
    for (int j=maxl-1; j>=0; j--)
        if ( P->Fa[j] && P->Fa[j]->val>=y ) P=P->Fa[j];
    return P;
}

bool Query(Seg *root,int L,int R,int x,int y)
{
    if ( y<L || R<x || root==tree ) return false;
    if ( x<=L && R<=y ) return root->sum;

    int mid=(L+R)>>1;
    bool fL=Query(root->lson,L,mid,x,y);
    bool fR=Query(root->rson,mid+1,R,x,y);
    return ( fL || fR );
}

bool Judge(int len)
{
    Tnode *node=Jump(c,len);
    return Query(node->Seg_Root,1,n+1,b+len-1,A);
}

int Binary()
{
    int L=0,R=min(A-b+1,c-d+1)+1;
    while (L+1<R)
    {
        int mid=(L+R)>>1;
        if ( Judge(mid) ) L=mid;
        else R=mid;
    }
    return L;
}

int main()
{
    freopen("4556.in","r",stdin);
    freopen("4556.out","w",stdout);

    scanf("%d%d",&n,&m);
    scanf("%s",&s);
    for (int i=1; i<=n; i++) a[i]=s[i-1]-'a';
    for (int i=1; i<=(n>>1); i++) swap(a[i],a[n-i+1]);

    New_Seg();
    Build_SAM();

    for (int i=1; i<=m; i++)
    {
        scanf("%d%d%d%d",&A,&b,&c,&d);
        A=n-A+1;
        b=n-b+1;
        c=n-c+1;
        d=n-d+1;
        int ans=Binary();
        printf("%d\n",ans);
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值