[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个字符串 ...
随机推荐
- 如何在Maven和Gradle中配置使用Groovy 2.4与Spock 1.0
如何在Maven和Gradle中配置使用Groovy 2.4与Spock 1.0 原文 https://dzone.com/articles/spock-10-groovy-24 翻译 hxfiref ...
- Django 创建第一个Project — Django学习(二)
检查django If Django is installed, you should see the version of your installation. If it isn’t, you’l ...
- ### mysql系统结构_3_Mysql_Learning_Notes
mysql系统结构_3_Mysql_Learning_Notes 存储层,内存结构 全局(buferpool) 只分配一次 全局共享 连接/会话(session) 针对每个会话/线程分配 按需动态分配 ...
- nginx自定义500,502,504错误页面无法跳转【转】
1.自定一个页面,这个页面是一个链接地址可以直接访问的. 以下是nginx的配置: location / { proxy_pass http://tomcat_app108; ...
- linux用户权限 -> 系统特殊权限
set_uid 运行一个命令的时候,相当于这个命令的所有者,而不是执行者的身份. suid的授权方法 suid 权限字符s(S),用户位置上的x位上设置. 授权方法: passwd chmod u+s ...
- RedisTemplate使用
RedisTemplate中定义了对5种数据结构操作 redisTemplate.opsForValue();//操作字符串 redisTemplate.opsForHash();//操作hash r ...
- 经典面试题:js继承方式下
上一篇讲解了构造函数的继承方式,今天来讲非构造函数的继承模式. 一.object()方法 json格式的发明人Douglas Crockford,提出了一个object()函数,可以做到这一点. fu ...
- opencv(1)图像处理
2.图像操作 图片裁剪 裁剪是利用array自身的下标截取实现 HSV空间 除了区域,图像本身的属性操作也非常多,比如可以通过HSV空间对色调和明暗进行调节.HSV空间是由美国的图形学专家A. R. ...
- HDU 1068 Girls and Boys(最大独立集)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1068 题目大意:有n个人,一些人认识另外一些人,选取一个集合,使得集合里的每个人都互相不认识,求该集合 ...
- (转载)使用SQL-Server创建一个银行数据管理系统Ⅰ
首先,要创建一个完整的数据管理系统,不是一蹴而就的,一定要要一步一步的来,不断完善,最终方能达到自己想要的结果,所以我在这里也是一点一点分步来做的. 创建数据库,数据库属性在这里用的是默认(不推荐使用 ...