题目

也是可以用\(SAM\)来做的

我们发现要求原串不相交,那么就要求在差分序列里不相交并且不相邻

考虑一下\(SAM\),暴力做法自然是对每一个节点统计其所有\(endpos\)的影响

既然这样我们为什么不直接启发式合并加线段树合并分类讨论一下呢

于是可休闲了

我们考虑往一个节点里插入一个新的\(endpos\)会产生什么影响

  1. 对于那些\(x-endpos>=mxlen+1\)的\(x\),我们这样插入显然是会产生\(x\)的贡献

  2. 如果\(x-endpos<mxlen+1\),那么就会产生\(|x-enspos|-1\)的贡献

分情况讨论一下这些贡献就好了

于是同时维护动态开点线段树就好了,合并直接线段树合并

代码

#include<tr1/unordered_map>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
#define LL long long
#pragma GCC optimize(3)
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("Ofast,no-stack-protector")
const int maxn=6e5+5;
const int R=2e5+5;
const int M=2.2e7;
using namespace std::tr1;
inline int read() {
char c=getchar();int x=0,r=1;
while(c<'0'||c>'9') {if(c=='-') r=-1;c=getchar();}
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return r*x;
}
int n,cnt=1,lst=1,tot,tax[maxn>>1],A[maxn];
unordered_map<int,int> son[maxn];
int len[maxn],fa[maxn];
std::vector<int> pos[maxn];
int a[maxn>>1];
int rt[maxn],l[M],r[M],t[M];
int st[maxn>>1],top;
LL d[M],ans,D,T;
int change(int now,int x,int y,int pos) {
if(!now) {
if(!top) now=++tot;
else now=st[top--];
}
t[now]++,d[now]+=pos;
if(x==y) return now;
int mid=x+y>>1;
if(pos<=mid) l[now]=change(l[now],x,mid,pos);
else r[now]=change(r[now],mid+1,y,pos);
return now;
}
LL ask(int now,int x,int y,int lx,int ry) {
if(!now||x>y) return 0;
if(x<=lx&&y>=ry) return t[now];
int mid=lx+ry>>1;
if(y<=mid) return ask(l[now],x,y,lx,mid);
if(x>mid) return ask(r[now],x,y,mid+1,ry);
return ask(l[now],x,y,lx,mid)+ask(r[now],x,y,mid+1,ry);
}
void query(int now,int x,int y,int lx,int ry) {
if(!now||x>y) return;
if(x<=lx&&y>=ry) {T+=t[now],D+=d[now];return;}
int mid=lx+ry>>1;
if(x<=mid) query(l[now],x,y,lx,mid);
if(y>=mid+1) query(r[now],x,y,mid+1,ry);
}
inline void ins(int c,int k) {
int p=++cnt,f=lst;lst=p;
len[p]=len[f]+1;
pos[p].push_back(k);rt[p]=change(rt[p],1,n-1,k);
while(f&&!son[f][c]) son[f][c]=p,f=fa[f];
if(!f) {fa[p]=1;return;}
int x=son[f][c];
if(len[f]+1==len[x]) {fa[p]=x;return;}
int y=++cnt;len[y]=len[f]+1;
fa[y]=fa[x],fa[x]=fa[p]=y;son[y]=son[x];
while(f&&son[f][c]==x) son[f][c]=y,f=fa[f];
}
int merge(int a,int b,int x,int y) {
if(!a||!b) return a+b;
if(x==y) {t[a]+=t[b];d[a]+=d[b];}
int mid=x+y>>1;
l[a]=merge(l[a],l[b],x,mid);r[a]=merge(r[a],r[b],mid+1,y);
t[a]=t[l[a]]+t[r[a]],d[a]=d[l[a]]+d[r[a]];
return a;
}
int main() {
n=read();
for(re int i=1;i<=n;++i) a[i]=read();
for(re int i=1;i<n;++i) ins(a[i+1]-a[i],i);
for(re int i=1;i<=cnt;++i) tax[len[i]]++;
for(re int i=1;i<n;i++) tax[i]+=tax[i-1];
for(re int i=cnt;i>=0;--i) A[tax[len[i]]--]=i;
for(re int i=cnt;i;--i) {
int x=A[i],F=fa[x];
if(pos[x].size()>pos[F].size()) std::swap(pos[x],pos[F]),std::swap(rt[x],rt[F]);
for(re int j=0;j<pos[x].size();j++) {
int now=pos[x][j];
ans+=ask(rt[F],now+1+len[F],n-1,1,n-1)*(LL)len[F];
ans+=ask(rt[F],1,now-len[F]-1,1,n-1)*(LL)len[F]; T=D=0;query(rt[F],now-len[F],now-1,1,n-1);
ans+=T*((LL)now-1)-D; T=D=0;query(rt[F],now+1,now+len[F],1,n-1);
ans+=D-T*((LL)now+1); pos[F].push_back(now);
}
rt[F]=merge(rt[F],rt[x],1,n-1);
}
ans+=(LL)(n-1)*n/2ll;
printf("%lld\n",ans);
return 0;
}

【LGP5161】WD与数列的更多相关文章

  1. luogu P5161 WD与数列 SAM 线段树合并 启发式合并

    LINK:WD与数列 这道题可谓妙绝 我明白了一个增量统计的原理. 原本的想法是:差分之后 显然长度为1的单独统计 长度为2的以及更多就是字符串之间的匹配问题了. 对差分序列建立SAM 由于第一个是一 ...

  2. 【LUOGU???】WD与数列 sam 启发式合并

    题目大意 给你一个字符串,求有多少对不相交且相同的子串. 位置不同算多对. \(n\leq 300000\) 题解 先把后缀树建出来. DFS 整棵树,维护当前子树的 right 集合. 合并两个集合 ...

  3. [Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)

    https://blog.csdn.net/WAautomaton/article/details/85057257 解法一:后缀数组 显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n ...

  4. P5161 WD与数列(后缀自动机+线段树合并)

    传送门 没想出来→_→ 首先不难看出要差分之后计算不相交也不相邻的相等子串对数,于是差分之后建SAM,在parent树上用线段树合并维护endpos集合,然后用启发式合并维护一个节点对另一个节点的贡献 ...

  5. SAM(后缀自动机)总结

    “写sam是肯定会去写的,这样才学的了字符串,后缀数组又不会用 >ω<, sam套上数据结构的感觉就像回家一样! 里面又能剖分又能线段树合并,调试又好调,我爱死这种写法了 !qwq” SA ...

  6. C#求斐波那契数列第30项的值(递归和非递归)

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  7. BZOJ1500[NOI2005]维修数列

    Description Input 输入的第1 行包含两个数N 和M(M ≤20 000),N 表示初始时数列中数的个数,M表示要进行的操作数目.第2行包含N个数字,描述初始时的数列.以下M行,每行一 ...

  8. PAT 1049. 数列的片段和(20)

    给定一个正数数列,我们可以从中截取任意的连续的几个数,称为片段.例如,给定数列{0.1, 0.2, 0.3, 0.4},我们有(0.1) (0.1, 0.2) (0.1, 0.2, 0.3) (0.1 ...

  9. 斐波拉契数列加强版——时间复杂度O(1),空间复杂度O(1)

    对于斐波拉契经典问题,我们都非常熟悉,通过递推公式F(n) = F(n - ) + F(n - ),我们可以在线性时间内求出第n项F(n),现在考虑斐波拉契的加强版,我们要求的项数n的范围为int范围 ...

随机推荐

  1. Python——爬虫学习1

    爬虫了解一下 网络爬虫(Web crawler),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本. Python的安装 本篇教程采用Python3 来写,所以你需要给你的电脑装上Python ...

  2. 九、curator recipes之不可重入锁InterProcessSemaphoreMutex

    简介 recipes的InterProcessSemaphoreMutex是一种不可重入的互斥锁,也就意味着即使是同一个线程也无法在持有锁的情况下再次获得锁,所以需要注意,不可重入的锁很容易在一些情况 ...

  3. 撩课-Java每天5道面试题第21天

    136.请解释Spring Bean的生命周期? 首先说一下Servlet的生命周期: 实例化, 初始init, 接收请求service, 销毁destroy: Spring上下文中的Bean生命周期 ...

  4. 新建Java Web项目

    1.MyEclipse的菜单栏--File--Web Project,新建一个web工程 Project name 填上自己的项目名称,例如HelloWorld. 需要选择Java EE版本以及Tar ...

  5. SQL修改表结构

    --(1)向数据库Student表中添加Name字段 use MR_NXT alter table student add Name char(20) --(2)将Student表中Name的字段的数 ...

  6. windows查看当前python的版本

    1.Ctrl+R打开控制台 输入python之后回车

  7. SPOJ:SUBLEX - Lexicographical Substring Search

    题面 第一行给定主串\((len<=90000)\) 第二行给定询问个数\(T<=500\) 随后给出\(T\)行\(T\)个询问,每次询问排名第\(k\)小的串,范围在\(int\)内 ...

  8. 教程:让你的表单升级到CSS3和HTML5客户端验证

    今天我们一起来看看如何创建一个实用并且功能强大的表单,表单使用如今最热门的技术HTML5和css3来创建,并且可以通过HTML5进行客户端验证. 查看预览下载附件 第一步:策划表单功能 首先,我们得为 ...

  9. drupal7 jquery脚本忽然不运行

    jquery脚本经过调试,确认没有错误,但是最最近一次,调整了引入的次序,目的是方便我识别哪些js是我自己写的,哪些是前端给的,便于后期维护时,迅速找到自己写的部分. 调整引入次序前: 调整后(调整后 ...

  10. IOS CALayer的属性和使用

    一.CALayer的常用属性 1.@propertyCGPoint position; 图层中心点的位置,类似与UIView的center:用来设置CALayer在父层中的位置:以父层的左上角为原点( ...