[Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)
https://blog.csdn.net/WAautomaton/article/details/85057257
解法一:后缀数组
显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n-1)/2。
答案=重复子串个数-相邻或相交重复子串个数。
前者单调栈直接求解,注意细节,重点在后者。
由于是有关相交的计数问题,考虑类似[NOI2016]优秀的拆分的设关键点的做法。
枚举两个串的偏移量k,每k个位置设一个关键点,我们需要保证任意两个相距为k的重复子串都在且仅在它们覆盖的第一个关键点处被计算一次。
求出每相邻两个关键点的LCP和LCS,发现答案是一个等差数列,注意式子的推导,不能重复计算。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
typedef long long ll;
using namespace std; const int N=;
int n,tot,top,s[N],b[N],stk[N],lg[N];
ll res,sm; struct SA{
int s[N],c[N],x[N],y[N],sa[N],rk[N],st[N][],h[N];
bool Cmp(int a,int b,int l){ return a+l<=n && b+l<=n && y[a]==y[b] && y[a+l]==y[b+l]; } void build(int m){
memset(y,,sizeof(y));
rep(i,,m) c[i]=;
rep(i,,n) c[x[i]=s[i]]++;
rep(i,,m) c[i]+=c[i-];
for (int i=n; i; i--) sa[c[x[i]]--]=i;
for (int k=,p=; p<n; k<<=,m=p){
p=;
rep(i,n-k+,n) y[++p]=i;
rep(i,,n) if (sa[i]>k) y[++p]=sa[i]-k;
rep(i,,m) c[i]=;
rep(i,,n) c[x[y[i]]]++;
rep(i,,m) c[i]+=c[i-];
for (int i=n; i; i--) sa[c[x[y[i]]]--]=y[i];
rep(i,,n) y[i]=x[i]; x[sa[p=]]=;
rep(i,,n) x[sa[i]]=Cmp(sa[i-],sa[i],k) ? p : ++p;
}
} void height(){
int k=;
rep(i,,n) rk[sa[i]]=i;
rep(i,,n){
for (int j=sa[rk[i]-]; i+k<=n && j+k<=n && s[i+k]==s[j+k]; k++);
h[rk[i]]=k; if (k) k--;
}
rep(i,,n) st[i][]=h[i];
rep(j,,lg[n]) rep(i,,n-(<<j)+) st[i][j]=min(st[i][j-],st[i+(<<(j-))][j-]);
} int que(int a,int b){
int x=rk[a],y=rk[b];
if (x==y) return n-a+;
if (x>y) swap(x,y);
x++; int t=lg[y-x+];
return min(st[x][t],st[y-(<<t)+][t]);
}
}sa1,sa2; int LCP(int l,int r){ return sa1.que(l,r); }
int LCS(int l,int r){ return sa2.que(n-r+,n-l+); } int main(){
freopen("P5161.in","r",stdin);
freopen("P5161.out","w",stdout);
scanf("%d",&n);
rep(i,,n) lg[i]=lg[i>>]+;
rep(i,,n) scanf("%d",&s[i]);
n--; rep(i,,n) b[i]=s[i]=s[i+]-s[i];
sort(b+,b+n+); tot=unique(b+,b+n+)-b-;
rep(i,,n) s[i]=lower_bound(b+,b+tot+,s[i])-b;
rep(i,,n) sa1.s[i]=sa2.s[n-i+]=s[i];
sa1.build(tot); sa2.build(tot); sa1.height(); sa2.height();
rep(i,,n+){
res+=sm;
for (; top && sa1.h[stk[top]]>=sa1.h[i]; top--)
sm-=1ll*(sa1.h[stk[top]]-sa1.h[i])*(stk[top]-stk[top-]);
sm+=sa1.h[stk[++top]=i];
}
rep(i,,n){
for (int j=i; j+i<=n; j+=i){
int a=min(i,LCS(j,j+i)),b=LCP(j,j+i),l=min(a-,b+a-i);
if (l>=) res-=1ll*(l+)*(b+a-i)-1ll*l*(l+)/;
}
}
printf("%lld\n",res+1ll*n*(n+)/);
return ;
}
解法二:后缀自动机+线段树合并
建出parent树,注意到任意两个位置的LCP就是它们在parent树上LCA的mx。于是每个结点上用一棵线段树维护相关信息,同时用一个vector记录子树中的点。线段树的合并直接用普通线段树合并,vector的合并用启发式合并。
复杂度$O(n\log^2 n)$,但由于常数小所以不会被卡。(当然用时是解法一的三倍)
#include<map>
#include<cstdio>
#include<vector>
#include<algorithm>
#define lson ls[x],L,mid
#define rson rs[x],mid+1,R
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,u) for (int i=h[u]; i; i=nxt[i])
typedef long long ll;
using namespace std; const int N=,M=;
ll ans,v[M],vs[M];
int n,p,np,lst=,nd=,nd2,cnt,a[N],pos[N],rt[N],mx[N];
int fa[N],ls[M],rs[M],to[N<<],nxt[N<<],h[N];
map<int,int>son[N];
vector<int>ve[N],*f[N];
void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } void ext(int c,int x){
p=lst; lst=np=++nd; mx[np]=mx[p]+; pos[x]=np;
while (p && !son[p][c]) son[p][c]=np,p=fa[p];
if (!p) fa[np]=;
else{
int q=son[p][c];
if (mx[q]==mx[p]+) fa[np]=q;
else{
int nq=++nd; mx[nq]=mx[p]+;
son[nq]=son[q]; fa[nq]=fa[q]; fa[q]=fa[np]=nq;
while (p && son[p][c]==q) son[p][c]=nq,p=fa[p];
}
}
} void mdf(int &x,int L,int R,int k){
if (!x) x=++nd2;
v[x]++; vs[x]+=k;
if (L==R) return;
int mid=(L+R)>>;
if (k<=mid) mdf(lson,k); else mdf(rson,k);
} ll que(int x,int L,int R,int l,int r,int k){
l=max(l,L); r=min(r,R);
if(!x || l>r || r< || l>n) return ;
if (L==l && r==R) return k ? vs[x] : v[x];
int mid=(L+R)>>;
if (r<=mid) return que(lson,l,r,k);
else if (l>mid) return que(rson,l,r,k);
else return que(lson,l,mid,k)+que(rson,mid+,r,k);
} int merge(int x,int y){
if (!x || !y) return x|y;
v[x]+=v[y]; vs[x]+=vs[y];
ls[x]=merge(ls[x],ls[y]);
rs[x]=merge(rs[x],rs[y]);
return x;
} void dfs(int u){
int k=mx[u];
For(i,u){
int v=to[i]; dfs(v);
if (f[u]->size()<f[v]->size()) swap(f[u],f[v]),swap(rt[u],rt[v]);
int ed=f[v]->size()-;
rep(j,,ed){
int x=f[v]->at(j);
ll A=que(rt[u],,n,x-k-,x-,)*(x-)-que(rt[u],,n,x-k-,x-,)+que(rt[u],,n,,x-k-,)*k;
ll B=que(rt[u],,n,x+,x+k+,)-que(rt[u],,n,x+,x+k+,)*(x+)+que(rt[u],,n,x+k+,n,)*k;
ans+=A+B;
}
rep(j,,ed) f[u]->push_back(f[v]->at(j));
rt[u]=merge(rt[u],rt[v]);
}
} int main(){
freopen("P5161.in","r",stdin);
freopen("P5161.out","w",stdout);
scanf("%d",&n);
rep(i,,n) scanf("%d",&a[i]);
n--; rep(i,,n) a[i]=a[i+]-a[i];
ans=1ll*n*(n+)>>;
rep(i,,n) ext(a[i],i);
rep(i,,nd) add(fa[i],i);
rep(i,,nd) f[i]=&ve[i];
rep(i,,n) f[pos[i]]->push_back(i),mdf(rt[pos[i]],,n,i);
dfs(); printf("%lld\n",ans);
return ;
}
[Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)的更多相关文章
- BZOJ3413: 匹配(后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...
- cf666E. Forensic Examination(广义后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...
- 模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合)
模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合) Code: #include <bits/stdc++.h> using namespace std; #define ...
- 【BZOJ4556】[TJOI2016&HEOI2016] 字符串(后缀自动机+线段树合并+二分)
点此看题面 大致题意: 给你一个字符串\(s\),每次问你一个子串\(s[a..b]\)的所有子串和\(s[c..d]\)的最长公共前缀. 二分 首先我们可以发现一个简单性质,即要求最长公共前缀,则我 ...
- bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)
bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...
- 【BZOJ-4556】字符串 后缀数组+二分+主席树 / 后缀自动机+线段树合并+二分
4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 657 Solved: 274[Su ...
- HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)
题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点, ...
- CF 666E Forensic Examination——广义后缀自动机+线段树合并
题目:http://codeforces.com/contest/666/problem/E 对模式串建广义后缀自动机,询问的时候把询问子串对应到广义后缀自动机的节点上,就处理了“区间”询问. 还要处 ...
- CF666E Forensic Examination(后缀自动机+线段树合并)
给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[pl..pr]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 我们首先对m个字符串 ...
随机推荐
- 20165320 预备作业2:技能学习心得与C语言学习
一.技能学习心得 1.你有什么技能比大多数人好? 我觉得我的篮球打得比一般的人好吧,但是也仅仅掌握了大部分基本的篮球技巧,算不上精通. 2.针对这个技能的获取你有什么成功的经验? 我觉得要打好篮球需要 ...
- 【bzoj题解】1008 越狱
题目描述 监狱有连续编号为1...N的N个房间,每个房间关押一个犯人,有M种宗教,每个犯人可能信仰其中一种.如果相邻房间的犯人的宗教相同,就可能发生越狱,求有多少种状态可能发生越狱. 输入 输入两个整 ...
- Linux硬盘镜像获取与还原(dd、AccessData FTK Imager)
1.硬盘镜像获取工具:dd dd是Linux/UNIX 下的一个非常有用的命令,作用是用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换. 1.1 本地取数据 查看磁盘及分区 # fdisk - ...
- Python类相关的装饰器
一.装饰器装饰类方法 from functools import wraps def wrapper(func): @wraps(func) def inner(self,*args,**kwargs ...
- CentOS7 修改网卡名称
vi /etc/sysconfig/grub 增加net.ifnames=0 biosdevname=0 执行:grub2-mkconfig -o /boot/grub2/grub.cfg
- java基础56 HTML5的标签知识(网页知识)
本文知识点(目录): 1.html常用标签 2.html实体标签 3.html媒体标签 4.html超链接标签 5.html图片标签 6.html标个标签 7.html框 ...
- No.3 selenium学习之路之鼠标&键盘事件
鼠标事件 from selenium.webdriver.common.action_chains import ActionChains contest_click() 右击 double_cli ...
- luoguP2735 电网 Electric Fences
一道校内模拟赛遇见的题 ** 不会正解就真的很麻烦的 数学题 ** 有一种东西叫 皮克定理 发现的千古神犇: 姓名:George Alexander Pick(所以叫皮克定理呀 国籍:奥地利(蛤!竟然 ...
- 第一篇CodeIgniter框架的下载及安装
初次学习Php,网上搜了很多php框架,最后选择了CodeIgniter. 安装环境:php5+mysql6.5+iis7 我的电脑是用来办公写文档用的,win7系统,不想换系统,所以就安装了win7 ...
- 安装VM虚拟机提示 尝试创建目录 C:\Public\documents\SharedVirtual Machines 时发生错误解决方法
把Windows Defender安全中心的“受控制文件夹的访问”给关闭了,然后就可以顺利安装上了. 作者:耑新新,发布于 博客园 转载请注明出处,欢迎邮件交流:zhuanxinxin@foxmai ...