题目描述

  有一个字符串\(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;
}

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

  1. 力扣算法:125-验证回文串,131-分割回文串---js

    LC 125-验证回文串 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写. 说明:本题中,我们将空字符串定义为有效的回文串. 注:回文串是正着读和反着读都一样的字符串. ...

  2. 算法笔记--树的直径 && 树形dp && 虚树 && 树分治 && 树上差分 && 树链剖分

    树的直径: 利用了树的直径的一个性质:距某个点最远的叶子节点一定是树的某一条直径的端点. 先从任意一顶点a出发,bfs找到离它最远的一个叶子顶点b,然后再从b出发bfs找到离b最远的顶点c,那么b和c ...

  3. [YNOI2017][bzoj4811][luogu3613] 由乃的OJ/睡觉困难综合症 [压位+树链剖分+线段树]

    题面 BZOJ题面,比较不清晰 Luogu题面,写的比较清楚 思路 原题目 我们先看这道题的原题目NOI2014起床困难综合症 的确就是上树的带修改版本 那么我们先来解决这个原版的序列上单次询问 二进 ...

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

    3589: 动态树 Time Limit: 30 Sec  Memory Limit: 1024 MBSubmit: 405  Solved: 137[Submit][Status][Discuss] ...

  5. 【BZOJ2243】【SDOI2011】染色(树链剖分,线段树)

    题面 我们也要换个花样,这回提供洛谷的题面 题解 线段树+树链剖分大水题 维护颜色段的方法很简单呀... 维护当前区间内的颜色段个数, 以及当前区间左端和右端的颜色, 合并的时候考虑是否要减一下就行了 ...

  6. LightOJ 1348 (树链剖分 + 线段树(树状数组))

    题目 Link 分析 典型的树链剖分题, 树链剖分学习资料 Code #include <bits/stdc++.h> using namespace std; const int max ...

  7. BZOJ3159决战——树链剖分+非旋转treap(平衡树动态维护dfs序)

    题目描述 输入 第一行有三个整数N.M和R,分别表示树的节点数.指令和询问总数,以及X国的据点. 接下来N-1行,每行两个整数X和Y,表示Katharon国的一条道路. 接下来M行,每行描述一个指令或 ...

  8. 树链剖分 (求LCA,第K祖先,轻重链剖分、长链剖分)

      2020/4/30   15:55 树链剖分是一种十分实用的树的方法,用来处理LCA等祖先问题,以及对一棵树上的节点进行批量修改.权值和查询等有奇效. So, what is 树链剖分? 可以简单 ...

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

    题目链接:http://codeforces.com/problemset/problem/504/E 题意:给出一棵树,每个结点上有一个字母.每个询问给出两个路径,问这两个路径的串的最长公共前缀. ...

随机推荐

  1. docker创建nginx+php-fpm+mysql环境(一分钟搭建lnmp)

    下载镜像 docker pull bitnami/php-fpm #下载php-fpm镜像 docker pull nginx #下载nginx镜像docker pull mysql:5.5.59 # ...

  2. 接口自动化框架(Pytest+request+Allure)

    前言: 接口自动化是指模拟程序接口层面的自动化,由于接口不易变更,维护成本更小,所以深受各大公司的喜爱. 接口自动化包含2个部分,功能性的接口自动化测试和并发接口自动化测试. 本次文章着重介绍第一种, ...

  3. Day3 Python基础之while、for循环(二)

    1.数据运算 算数运算 整除运算:// 取余运算:% 指数运算:** 赋值运算 b+=a;等价于b=b+a 比较运算 >,<,==,!=,>=,<= 逻辑运算符 and .or ...

  4. HDU 2003 求绝对值

    http://acm.hdu.edu.cn/showproblem.php?pid=2003 Problem Description 求实数的绝对值.   Input 输入数据有多组,每组占一行,每行 ...

  5. Day 3-4 函数进阶

    1.名称空间 定义:Python使用叫做命名空间的东西来记录变量的轨迹.命名空间是一个 字典(dictionary) ,它的键就是变量名,它的值就是那些变量的值.是存放变量和值的内存地址的绑定关系的空 ...

  6. Android——Activity的简绍

    Activity Activity的运行机制其实和JavaEE中的servlet很像,而我们的Android系统也就相当与其servlet容器,Activity在其中进行创建实例.初始化.运行.销毁等 ...

  7. Could not render e, see the console.

    错误截图: 解决: 在application.properties中开启swagger swagger2.enable=true

  8. vs code安装

    vs code是一款文本编辑器,开源,是前端界的vs,而Dreamweaver适合入门. user版本的一些系统分区文件夹无法创建,可能存在语言显示问题.一般用户建议使用system版. 下载链接:h ...

  9. Math java

    package cn.liuliu.com; import java.math.BigDecimal; import java.math.BigInteger; /* * 一.Math类? * * 1 ...

  10. 七、.net core下配置、数据库访问等操作实现

    配置读取 .net core下读取配置还是有点麻烦的,本身没有System.Configuration.dll,所以在进行配置前需要自行引用Microsoft.Extensions.Configura ...