【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\)
- \(transr~l_1~r_1~l_2~r_2\):和上一个操作类似,但是只允许在右边加入或删除字符
\]
\(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】回文串 树链剖分 回文自动机的更多相关文章
- 力扣算法:125-验证回文串,131-分割回文串---js
LC 125-验证回文串 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写. 说明:本题中,我们将空字符串定义为有效的回文串. 注:回文串是正着读和反着读都一样的字符串. ...
- 算法笔记--树的直径 && 树形dp && 虚树 && 树分治 && 树上差分 && 树链剖分
树的直径: 利用了树的直径的一个性质:距某个点最远的叶子节点一定是树的某一条直径的端点. 先从任意一顶点a出发,bfs找到离它最远的一个叶子顶点b,然后再从b出发bfs找到离b最远的顶点c,那么b和c ...
- [YNOI2017][bzoj4811][luogu3613] 由乃的OJ/睡觉困难综合症 [压位+树链剖分+线段树]
题面 BZOJ题面,比较不清晰 Luogu题面,写的比较清楚 思路 原题目 我们先看这道题的原题目NOI2014起床困难综合症 的确就是上树的带修改版本 那么我们先来解决这个原版的序列上单次询问 二进 ...
- 【BZOJ-3589】动态树 树链剖分 + 线段树 + 线段覆盖(特殊的技巧)
3589: 动态树 Time Limit: 30 Sec Memory Limit: 1024 MBSubmit: 405 Solved: 137[Submit][Status][Discuss] ...
- 【BZOJ2243】【SDOI2011】染色(树链剖分,线段树)
题面 我们也要换个花样,这回提供洛谷的题面 题解 线段树+树链剖分大水题 维护颜色段的方法很简单呀... 维护当前区间内的颜色段个数, 以及当前区间左端和右端的颜色, 合并的时候考虑是否要减一下就行了 ...
- LightOJ 1348 (树链剖分 + 线段树(树状数组))
题目 Link 分析 典型的树链剖分题, 树链剖分学习资料 Code #include <bits/stdc++.h> using namespace std; const int max ...
- BZOJ3159决战——树链剖分+非旋转treap(平衡树动态维护dfs序)
题目描述 输入 第一行有三个整数N.M和R,分别表示树的节点数.指令和询问总数,以及X国的据点. 接下来N-1行,每行两个整数X和Y,表示Katharon国的一条道路. 接下来M行,每行描述一个指令或 ...
- 树链剖分 (求LCA,第K祖先,轻重链剖分、长链剖分)
2020/4/30 15:55 树链剖分是一种十分实用的树的方法,用来处理LCA等祖先问题,以及对一棵树上的节点进行批量修改.权值和查询等有奇效. So, what is 树链剖分? 可以简单 ...
- CF 504E Misha and LCP on Tree(树链剖分+后缀数组)
题目链接:http://codeforces.com/problemset/problem/504/E 题意:给出一棵树,每个结点上有一个字母.每个询问给出两个路径,问这两个路径的串的最长公共前缀. ...
随机推荐
- 第四次oo博客
论述测试与正确性论证的效果差异 单元测试利用测试者构造的测试用例来检查类或方法的正确性,一般来说所需要测试的用例是无穷多的,通过人为构造代表性的测试用例来尽量测试所有代码.测试的优点在于不易出错,只要 ...
- JS 面向对象 ~ 创建对象的 9 种方式
一.创建对象的几种方式 1.通过字面量创建 var obj = {}; 这种写法相当于: var obj = new Object(); 缺点:使用同一个接口创建很多单个对象,会产生大量重复代码 2. ...
- p211有界自共轭算子T是实数集合的子集
对条件 取非 是 ∉谱集合的实数 才对 现在是 入 属于正则点集 他 然后 又说T 的谱是实数 这不矛盾吗 这里根据 必要性 推出 蓝色和红色矛盾 矛盾就是 这是谱点 然 ...
- pinpoint vs druid
主流Java数据库连接池分析(C3P0,DBCP,TomcatPool,BoneCP,Druid) - ppjj - 博客园 https://www.cnblogs.com/nizuimeiabc1/ ...
- python基础之数据类型和数值类型
python3的六大数据类型: 1.tuple元组 2.number数字 3.string字符串 4.set集合 5.list列表 6.dictionary字典 其中不可变数据3个:tuple.num ...
- java中的标记接口(标签接口)
Java中的标记接口(Marker Interface),又称标签接口(Tag Interface),具体是不包含任何方法的接口.在Java中很容易找到标记接口的例子,比如JDK中的Serialzab ...
- bootstrap3兼容IE8
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Linux基础学习笔记6-SHELL编程
编程基础 程序:指令+数据 程序编程风格: 过程式:以指令为中心,数据服务于指令 对象式:以数据为中心,指令服务于数据 shell程序:提供了编程能力,解释执行 编程基本概念: 顺序执行:循环执行:选 ...
- Netty ByteBuf和Nio ByteBuffer
参考https://blog.csdn.net/jeffleo/article/details/69230112 一.简介 Netty中引入了ByteBuf,它相对于ByteBuffer来说,带来了很 ...
- Java面向对象之多态的静态和动态实现
简单而言: 静态多态:即为重载,方法的重载 动态多态:即为重写/覆盖,方法的重写