【XSY2715】回文串 树链剖分 回文自动机

题目描述

  有一个字符串\(s\),长度为\(n\)。有\(m\)个操作:

  • \(addl ~c\):在\(s\)左边加上一个字符\(c\)
  • \(addr~c\):在\(s\)右边加上一个字符
  • \(transl~l_1~r_1~l_2~r_2\):有两个\(s\)的子串\(s_1=s[l_1\ldots r_1],s_2=s[l_2\ldots r_2]\)。你要把\(s_1\)变成\(s_2\)。每次允许在左边加一个字符或删一个字符。要求操作次数最少。定义一个字符串是好的当且仅当这个字符串是回文串且在操作过程中出现。记

\[
ans=\sum_{i=1}^n\sum_{j=i}^n[s[i\ldots j]~is~good]\times (j-i+1)
\]

  求\(ans\)

  • \(transr~l_1~r_1~l_2~r_2\):和上一个操作类似,但是只允许在右边加入或删除字符

\[
<Empty \space Math \space Block>
\]

  \(n,m\leq 100000\)

题解

  毒瘤题。

  先把所有操作保存下来,求出最终的字符串。

  观察到操作过程中每个字符串都只会出现一次,而且左边的一部分相同。

  可以用回文自动机完成操作。

  先把回文自动机建出来,把fail树剖分。

  处理出每个前缀/后缀的最长回文子串。

  询问时先在fail树上面跳得到在范围内的最长回文子串。

  那么答案就是这条链上所有回文串的出现次数\(\times\)长度的和。

  如果lca不是这两个串的lcp,那么就要减掉lca的值。

  插入时找到在当前范围内的最长回文前缀/后缀,把这个点到根上所有字符串的出现次数\(+1\)

  时间复杂度:\(O((n+m)\log^2 n)\)

  其实开始给你的那个字符串可以在\(O(n)\)内预处理完成的,总的复杂度就是\(O(n+m\log^2 n)\),不过我懒得写了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
namespace pam
{
    int s[200010];
    int next[200010][30];
    int fail[200010];
    int len[200010];
    int last;
    int n;
    int p;
    void init(int x)
    {
        int i;
        for(i=1;i<=26;i++)
            next[x][i]=2;
    }
    void init()
    {
        init(1);
        init(2);
        len[1]=-1;
        fail[1]=2;
        len[2]=0;
        fail[2]=1;
        s[0]=0;
        p=2;
        n=0;
        last=2;
    }
    void insert(int x)
    {
        s[++n]=x;
        while(s[n-len[last]-1]!=s[n])
            last=fail[last];
        int cur=last;
        if(next[cur][x]==2)
        {
            int now=++p;
            init(now);
            len[now]=len[cur]+2;
            last=fail[last];
            while(s[n-len[last]-1]!=s[n])
                last=fail[last];
            fail[now]=next[last][x];
            next[cur][x]=now;
        }
        last=next[cur][x];
    }
    void rebuild()
    {
        last=2;
        n=0;
    }
    void insert2(int x)
    {
        s[++n]=x;
        while(s[n-len[last]-1]!=s[n])
            last=fail[last];
        int cur=last;
        last=next[cur][x];
    }
}
namespace sgt
{
    struct node
    {
        int l,r,ls,rs;
        ll v;
        ll s;
        int t;
    };
    node a[400010];
    int n;
    int rt;
    void build(int &p,int l,int r,int *v)
    {
        p=++n;
        a[p].l=l;
        a[p].r=r;
        if(l==r)
        {
            a[p].v=v[l];
            return;
        }
        int mid=(l+r)>>1;
        build(a[p].ls,l,mid,v);
        build(a[p].rs,mid+1,r,v);
        a[p].v=a[a[p].ls].v+a[a[p].rs].v;
    }
    void add(int p,int t)
    {
        a[p].t+=t;
        a[p].s+=a[p].v*t;
    }
    void push(int p)
    {
        if(a[p].t)
        {
            add(a[p].ls,a[p].t);
            add(a[p].rs,a[p].t);
            a[p].t=0;
        }
    }
    void add(int p,int l,int r,int v)
    {
        if(l<=a[p].l&&r>=a[p].r)
        {
            add(p,v);
            return;
        }
        push(p);
        int mid=(a[p].l+a[p].r)>>1;
        if(l<=mid)
            add(a[p].ls,l,r,v);
        if(r>mid)
            add(a[p].rs,l,r,v);
        a[p].s=a[a[p].ls].s+a[a[p].rs].s;
    }
    ll query(int p,int l,int r)
    {
        if(l<=a[p].l&&r>=a[p].r)
            return a[p].s;
        int mid=(a[p].l+a[p].r)>>1;
        push(p);
        ll s=0;
        if(l<=mid)
            s+=query(a[p].ls,l,r);
        if(r>mid)
            s+=query(a[p].rs,l,r);
        return s;
    }
}
using sgt::rt;
namespace tree
{
    struct graph
    {
        int h[200010];
        int v[200010];
        int t[200010];
        int n;
        void add(int x,int y)
        {
            n++;
            v[n]=y;
            t[n]=h[x];
            h[x]=n;
        }
    };
    graph g;
    void addedge(int x,int y)
    {
        g.add(x,y);
    }
    int f[200010];
    int d[200010];
    int w[200010];
    int t[200010];
    int s[200010];
    int ms[200010];
    int c[200010];
    int v[200010];
    int e[200010];
    int ti;
    void dfs1(int x,int fa,int dep)
    {
        f[x]=fa;
        d[x]=dep;
        s[x]=1;
        int mx=0;
        int i;
        for(i=g.h[x];i;i=g.t[i])
        {
            dfs1(g.v[i],x,dep+1);
            s[x]+=s[g.v[i]];
            if(s[g.v[i]]>mx)
            {
                mx=s[g.v[i]];
                ms[x]=g.v[i];
            }
        }
    }
    void dfs2(int x,int top)
    {
        t[x]=top;
        w[x]=++ti;
        c[ti]=x;
        e[ti]=v[x];
        if(!ms[x])
            return;
        dfs2(ms[x],top);
        int i;
        for(i=g.h[x];i;i=g.t[i])
            if(g.v[i]!=ms[x])
                dfs2(g.v[i],g.v[i]);
    }
    int find(int x,int y)
    {
        while(v[t[x]]>y)
            x=f[t[x]];
        return c[upper_bound(e+w[t[x]],e+w[x]+1,y)-e-1];
    }
    void add(int x,int v)
    {
        while(x)
        {
            sgt::add(rt,w[t[x]],w[x],v);
            x=f[t[x]];
        }
    }
    ll query(int x,int y)
    {
        ll s=0;
        while(t[x]!=t[y])
            if(d[t[x]]>d[t[y]])
            {
                s+=sgt::query(rt,w[t[x]],w[x]);
                x=f[t[x]];
            }
            else
            {
                s+=sgt::query(rt,w[t[y]],w[y]);
                y=f[t[y]];
            }
        if(w[x]<w[y])
            s+=sgt::query(rt,w[x],w[y]);
        else
            s+=sgt::query(rt,w[y],w[x]);
        return s;
    }
    int getlca(int x,int y)
    {
        while(t[x]!=t[y])
            if(d[t[x]]>d[t[y]])
                x=f[t[x]];
            else
                y=f[t[y]];
        return w[x]<w[y]?x:y;
    }
}
int op[100010],ql1[100010],qr1[100010],ql2[100010],qr2[100010];
int n,m;
int s[200010],s1[100010],s2[100010],s3[100010];
int pre[200010],suf[200010];
int nl,nr;
void addr()
{
    nr++;
    int x=tree::find(pre[nr],nr-nl+1);
    tree::add(x,1);
}
void addl()
{
    nl--;
    int x=tree::find(suf[nl],nr-nl+1);
    tree::add(x,1);
}
void queryl(int l1,int r1,int l2,int r2)
{
    int x=tree::find(pre[r1],r1-l1+1);
    int y=tree::find(pre[r2],r2-l2+1);
    int lca=tree::getlca(x,y);
    ll ans=tree::query(x,y);
    int len=tree::v[lca];
    if(r1-l1+1!=len&&r2-l2+1!=len&&s[r1-len]==s[r2-len])
        ans-=sgt::query(rt,tree::w[lca],tree::w[lca]);
    printf("%lld\n",ans);
}
void queryr(int l1,int r1,int l2,int r2)
{
    int x=tree::find(suf[l1],r1-l1+1);
    int y=tree::find(suf[l2],r2-l2+1);
    int lca=tree::getlca(x,y);
    ll ans=tree::query(x,y);
    int len=tree::v[lca];
    if(r1-l1+1!=len&&r2-l2+1!=len&&s[l1+len]==s[l2+len])
        ans-=sgt::query(rt,tree::w[lca],tree::w[lca]);
    printf("%lld\n",ans);
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("b.in","r",stdin);
    freopen("b.out","w",stdout);
#endif
    char str[10];
    scanf("%d%d",&n,&m);
    int n1=0,n2=0;
    int i;
    for(i=1;i<=n;i++)
    {
        scanf("%d",&s3[i]);
        s3[i]++;
    }
    for(i=1;i<=m;i++)
    {
        scanf("%s",str);
        if(str[0]=='a')
        {
            scanf("%d",&ql1[i]);
            ql1[i]++;
            if(str[3]=='l')
            {
                op[i]=1;
                s1[++n1]=ql1[i];
            }
            else
            {
                op[i]=2;
                s2[++n2]=ql1[i];
            }
        }
        else
        {
            scanf("%d%d%d%d",&ql1[i],&qr1[i],&ql2[i],&qr2[i]);
            if(str[5]=='l')
                op[i]=3;
            else
                op[i]=4;
        }
    }
    int num=0;
    for(i=m;i>=1;i--)
    {
        if(op[i]==1)
            num++;
        else if(op[i]>=2)
        {
            ql1[i]+=num;
            qr1[i]+=num;
            ql2[i]+=num;
            qr2[i]+=num;
        }
    }
    int len=0;
    for(i=n1;i>=1;i--)
        s[++len]=s1[i];
    for(i=1;i<=n;i++)
        s[++len]=s3[i];
    for(i=1;i<=n2;i++)
        s[++len]=s2[i];
    pam::init();
    for(i=1;i<=len;i++)
    {
        pam::insert(s[i]);
        pre[i]=pam::last;
    }
    pam::rebuild();
    for(i=len;i>=1;i--)
    {
        pam::insert2(s[i]);
        suf[i]=pam::last;
    }
    for(i=2;i<=pam::p;i++)
    {
        tree::addedge(pam::fail[i],i);
        tree::v[i]=pam::len[i];
    }
    tree::dfs1(1,0,1);
    tree::dfs2(1,1);
    sgt::build(rt,1,pam::p,tree::e);
    nl=n1+1;
    nr=n1;
    for(i=1;i<=n;i++)
        addr();
    for(i=1;i<=m;i++)
        if(op[i]==1)
            addl();
        else if(op[i]==2)
            addr();
        else if(op[i]==3)
            queryl(ql1[i],qr1[i],ql2[i],qr2[i]);
        else
            queryr(ql1[i],qr1[i],ql2[i],qr2[i]);
    return 0;
}

原文地址:https://www.cnblogs.com/ywwyww/p/8513554.html

时间: 2024-08-08 00:53:25

【XSY2715】回文串 树链剖分 回文自动机的相关文章

【BZOJ-3589】动态树 树链剖分 + 线段树 + 线段覆盖(特殊的技巧)

3589: 动态树 Time Limit: 30 Sec  Memory Limit: 1024 MBSubmit: 405  Solved: 137[Submit][Status][Discuss] Description 别忘了这是一棵动态树, 每时每刻都是动态的. 小明要求你在这棵树上维护两种事件 事件0: 这棵树长出了一些果子, 即某个子树中的每个节点都会长出K个果子. 事件1: 小明希望你求出几条树枝上的果子数. 一条树枝其实就是一个从某个节点到根的路径的一段. 每次小明会选定一些树枝

树链剖分(轻重链剖分)算法笔记[转]

仔细想想 自己第一次听说这个这个数据结构大概有两年半的时间了 然而一直不会. 不过现在再回头来看 发现其实也不是很麻烦 首先 在学树链剖分之前最好先把LCALCA 树形DPDP 以及dfsdfs序 这三个知识点学了 如果这三个知识点没掌握好的话 树链剖分难以理解也是当然的 ------------------------------------------------------------------------------------------- 树链剖分通常用于处理树的形态不变 但点权/

【数据结构】树链剖分

百度百科 Definition 在处理树上的链上修改与询问问题时,如果朴素地采用LCA的手段,那么询问的复杂度是\(O(logn)\),但是修改的复杂度会成为朴素地\(O(n)\),这在大部分题目中是难以接受的.用于处理树上两点间简单路径上权值和与单点子树权值和的修改以及其查询问题的数据结构与处理方法,被叫做树链剖分. Solution 考虑在一位的数列上做区间查询与修改,可以使用线段树做到每次修改\(O(logn)\)的复杂度.那么考虑在树上能否使用线段树.最简单的思想是给每个节点一个DFS序

数据结构之树链剖分

首先了解一下基本概念: 重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子.      轻儿子:v的其它子节点.      重边:点v与其重儿子的连边.      轻边:点v与其轻儿子的连边.      重链:由重边连成的路径.      轻链:轻边. 剖分后的树有如下性质:      性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v]:      性质2:从根到某一点的路径上轻链.重链的个数都不大于logN. 树链剖分,如其字面意思,就是将一棵树按照轻重

CF 504E Misha and LCP on Tree(树链剖分+后缀数组)

题目链接:http://codeforces.com/problemset/problem/504/E 题意:给出一棵树,每个结点上有一个字母.每个询问给出两个路径,问这两个路径的串的最长公共前缀. 思路:树链剖分,记录每条链的串,正反都记,组成一个大串.记录每条链对应的串在大串中的位置.然后对大串求后缀数组.最后询问就是在一些链上的查询. 树链剖分总是那么优秀.. const int N=600005; int next[N],node[N],head[N],e; void add(int u

【POJ3237】Tree 树链剖分

题意: change,把第i条边权值改成v negate,把a到b路径上所有权值取相反数(*(-1)) query,询问a到b路径上所有权值的最大值 树链剖分. 以前一直不会,但是我恶补LCT了,所以先学一下. 对于现在的水平来说,树剖太水了,自己翻资料吧,我只提供一个还算不错的代码. 扒代码的时候可以用我的这个. 附rand和pai. 代码: #include <cstdio> #include <cstring> #include <iostream> #inclu

【模板时间】◆模板&#183;II◆ 树链剖分

[模板·II]树链剖分 学长给我讲树链剖分,然而我并没有听懂,还是自学有用--另外感谢一篇Blog +by 自为风月马前卒+ 一.算法简述 树链剖分可以将一棵普通的多叉树转为线段树计算,不但可以实现对一棵子树的操作,还可以实现对两点之间路径的操作,或是求 LCA(看起来很高级). 其实树链剖分不算什么特别高难的算法,它建立在 LCA.线段树.DFS序 的基础上(如果不了解这些算法的还是先把这些算法学懂再看树链剖分吧 QwQ).又因为树链剖分的基础算法不难,树链剖分的题也逐渐被引入 OI 赛中.

BZOJ 2243: [SDOI2011]染色 树链剖分

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1886  Solved: 752[Submit][Status] Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完成这m个操作. In

bzoj 2243: [SDOI2011]染色 线段树区间合并+树链剖分

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 7925  Solved: 2975[Submit][Status][Discuss] Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完