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与数列(后缀数组/后缀自动机+线段树合并)的更多相关文章

  1. BZOJ3413: 匹配(后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...

  2. cf666E. Forensic Examination(广义后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...

  3. 模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合)

    模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合) Code: #include <bits/stdc++.h> using namespace std; #define ...

  4. 【BZOJ4556】[TJOI2016&HEOI2016] 字符串(后缀自动机+线段树合并+二分)

    点此看题面 大致题意: 给你一个字符串\(s\),每次问你一个子串\(s[a..b]\)的所有子串和\(s[c..d]\)的最长公共前缀. 二分 首先我们可以发现一个简单性质,即要求最长公共前缀,则我 ...

  5. bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)

    bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...

  6. 【BZOJ-4556】字符串 后缀数组+二分+主席树 / 后缀自动机+线段树合并+二分

    4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 657  Solved: 274[Su ...

  7. HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)

    题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点, ...

  8. CF 666E Forensic Examination——广义后缀自动机+线段树合并

    题目:http://codeforces.com/contest/666/problem/E 对模式串建广义后缀自动机,询问的时候把询问子串对应到广义后缀自动机的节点上,就处理了“区间”询问. 还要处 ...

  9. CF666E Forensic Examination(后缀自动机+线段树合并)

    给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[pl..pr]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 我们首先对m个字符串 ...

随机推荐

  1. log4net记录系统错误日志到文本文件用法详解

    log4net是一个完全免费开源的插件,可以去官网下载源码. 一般系统操作日志不会用log4net,自己写代码存入数据库更方便合理,但是系统部署后运行在客户环境,难免会发生系统bug.崩溃.断网等无法 ...

  2. UNIX网络编程 第5章 TCP客户/服务器程序示例

    UNIX网络编程 第5章 TCP客户/服务器程序示例

  3. Oracle数据库中几种常见的SCN

    控制文件中的SCN 数据文件头的SCN 数据块中的SCN 日志文件头中的SCN 事务SCN 内存中的SCN 一 控制文件中的SCN 1.1 数据库SCN 数据库SCN表示最近一次全量checkpoin ...

  4. 一个简单的java jdbc案例

    有些时候,配置一个spring+mybatis框架,然后写xml,dao ,service显得特别繁琐. 如果我们只是想查一下数据库,不考虑连接复用也不考虑动态sql,可以用原生的jdbc来实现,方便 ...

  5. 【C++】数组-二分法查找

    1.原理 对于给定值的查找,如果大于该数组的中间元素,下一步在元素值大的区域继续与其中间元素比较:否则下一步在元素值小的区域内继续查找,直到找到目标元素.如果到最后还没有找到,则输出"数组中 ...

  6. aarch64_n3

    ntp-doc-4.2.8p10-1.fc26.noarch.rpm 2017-03-24 02:07 1.2M fedora Mirroring Project ntp-perl-4.2.8p10- ...

  7. VC++ 编译libcurl 支持SSL,GZIP

    由于网上下载的 libcurl 不支持 gzip,只好自己动手编译,期间走了很多弯路,下面是最终成功的记录. 我所使用的环境 Visual Studio 2010 . Windows 7 64 bit ...

  8. EPC摘抄

    S6a MME – HSS 完成用户位置信息的交换和用户签约信息的管理,传送控制面信息 Diameter MME:主要负责信令处理及移动性管理,功能包括:NAS信令及其安全:跟踪区域(Tracking ...

  9. vue总结07 常用插件

    插件 开发插件 插件通常会为 Vue 添加全局功能.插件的范围没有限制——一般有下面几种: 添加全局方法或者属性,如: vue-custom-element 添加全局资源:指令/过滤器/过渡等,如 v ...

  10. centos7连接阿里云长时间连接不上

    一.手动修改网卡配置 手上有几台centos7的linux,当连接阿里云的ecs服务器时候长时间连接不上,最后失败的问题. 使用 -vvv参数到如下语句就卡着不动了 ssh -vvv XXX.XXX. ...