P6152 [集训队作业2018] 后缀树节点数 题解
后缀树节点数可以转化为反串的 SAM 节点数。我们把 SAM 上的节点分为前缀节点和分裂节点两类,前缀节点表示前缀字符串所在节点,其它为分裂节点。前缀节点个数很好统计,就是 \(r-l+1\)。
统计分裂节点时,我们可以用该节点的最长串代表该节点,那么该节点分裂的条件是:最长串在 \(l,r\) 中出现了至少两次,并且不是 \([l,r]\) 的前缀。我们令该串长为 \(len\),那么形式化表达条件就是:
- 存在一组 \(x\) 和 \(y\)满足 \(l\le x-len,l\le y-len,x\le r,y\le r,LCS(x,y)=len\)。
- \(l+len-1\notin endpos\)。
但是分裂节点的第二个条件很不好统计,所以我们可以把分裂节点的定义改为所有满足条件 1 的节点,最后再容斥掉同时是前缀节点和分裂节点的节点就可以了。
现在我们的第一步就是统计新定义的分裂节点个数。首先经典套路把 \(LCS(x,y)\) 转化为 \(x\),\(y\) 的前缀对应节点在 parent 树上的 LCA 的 \(len\)。然后我们可以枚举 LCA:\(u\),在 \(u\) 的两颗不同子树内找出 \(x\),\(y\) 两个点满足 \(l\le x-len(u),l\le y-len(u),x\le r,y\le r\),这里显然只有每组相邻的 \(x\),\(y\) 是有意义的。所以我们可以用 set 启发式合并支配对。那么现在我们就有了 \(O(n\log n)\) 个支配对 \((x,y,c)\),这里我们钦定 \(x<y\)。那么分裂节点就是所有满足 \(l\le x-len(c),y\le r\) 的支配对中不同的 \(c\) 的个数。
这个东西可以扫描线求,我们枚举 \(r\),对于固定的 \(r\),并设 \(f_l\) 为区间 \([l,r]\) 的答案,并记录 \(lst_c\) 表示 \(c\) 最后产生贡献的位置(可以结合后面操作理解)。那么我们加入所有 \(y=r\) 的支配对后,对于一个 \((x,y,c)\),我们需要进行的操作就是将 \([lst_c+1,x]\) 区间加 1,再将 \(lst_c\) 设成 \(x\),这个可以用树状数组维护差分数组。
最后就只剩下统计同时是前缀节点和分裂节点的节点了,显然如果 \([l,x]\) 是分裂节点,则 \([l,x-1]\) 也是分裂节点,所以我们可以二分找到最长的分裂节点。接下来考虑如何 check。我们可以定位到串 \([l,x]\) 所在的节点 \(p\),如果 \([l,x]\) 是分裂节点,说明在上面统计分裂节点时,\(p\) 是对 \(f_l\) 产生过贡献的,也就是 \(l\le lst_p\),定位到当前串所在节点可以用哈希。然后就做完了。
code:
代码
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/exception.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define ll long long
#define LL __int128
using namespace std;
using namespace __gnu_pbds;
const int N=3e5+5;
const LL base=5201314,mod=(1ll<<61)-1;
gp_hash_table<ll,int> mp;
struct node{int fa,len;gp_hash_table<int,int>v;}t[N];
struct Node{int v,l,r,id;}q[N*30];
int tot=1,n,m,lst=1,ans[N],col[N],ls[N],cnt,hd[N],nxt[N],tov[N],qcnt,siz[N],son[N],tr[N];
ll a[N];
LL qw[N],hs[N];
void upd(int x,int v){for(;x;x-=-x&x) tr[x]+=v;}
int get(int x){int s=0;for(;x<=n;x+=-x&x) s+=tr[x];return s;}
void add(int u,int v){nxt[++cnt]=hd[u],tov[cnt]=v,hd[u]=cnt;}
inline ll query(int l,int r){return ((LL)hs[r]-(LL)hs[l-1]*qw[r-l+1]%mod+mod)%mod;}
inline void Insert(int id,int c)
{
int p=lst,nw=lst=++tot;
t[nw].len=t[p].len+1,col[nw]=id;
while(p&&!t[p].v[c]) t[p].v[c]=nw,p=t[p].fa;
if(!p){t[nw].fa=1;return ;}
int q=t[p].v[c];
if(t[p].len+1==t[q].len) t[nw].fa=q;
else
{
int nq=++tot;t[nq]=t[q];
t[nq].len=t[p].len+1;
t[nw].fa=t[q].fa=nq;
while(p&&t[p].v[c]==q) t[p].v[c]=nq,p=t[p].fa;
}
}
int cmp(Node x,Node y){return x.r!=y.r?x.r<y.r:x.v>y.v;}
int pre(int u)
{
siz[u]=1;int p=col[u];
for(int i=hd[u],v;i;i=nxt[i])
{
v=tov[i];p=pre(v);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
mp[query(p-t[u].len+1,p)]=u;
return p;
}
set<int> g[N];
void Add(int x,int u)
{
auto it=g[u].lower_bound(x);
if(it!=g[u].end()) q[++qcnt]={1,x-t[u].len,*it,u};
if(it!=g[u].begin()) q[++qcnt]={1,*(--it)-t[u].len,x,u};
}
void dfs1(int u)
{
if(son[u]) dfs1(son[u]),swap(g[son[u]],g[u]);
if(col[u]) Add(col[u],u),g[u].insert(col[u]);
for(int i=hd[u],v;i;i=nxt[i]) if((v=tov[i])!=son[u])
{
dfs1(v);
for(auto x:g[v]) Add(x,u);
for(auto x:g[v]) g[u].insert(x);
}
}
inline bool check(int l,int r){ll s=query(l,r);return !(mp.find(s)==mp.end()||l>ls[mp[s]]);}
inline ll rd()
{
char c;int f=1;
while(!isdigit(c=getchar())) if(c=='-')f=-1;
ll x=c-'0';
while(isdigit(c=getchar())) x=x*10+(c^48);
return x*f;
}
int main()
{
n=rd(),m=rd(),qw[0]=1;
for(int i=1;i<=n;i++) a[n-i+1]=rd();
for(int i=1;i<=n;i++)
{
Insert(i,a[i]);
qw[i]=(LL)qw[i-1]*base%mod;
hs[i]=((LL)hs[i-1]*base+a[i]+1314520)%mod;
}
for(int i=2;i<=tot;i++) add(t[i].fa,i);
pre(1),dfs1(1);
for(int i=1,l,r;i<=m;i++) l=rd(),r=rd(),q[++qcnt]={0,n-r+1,n-l+1,i};
sort(q+1,q+qcnt+1,cmp);
for(int i=1;i<=qcnt;i++)
{
int l=q[i].l,r=q[i].r,id=q[i].id;
if(q[i].v==0)
{
int s=r-l+1+get(l),L=l-1,R=r;
while(L<R)
{
int mid=(L+R+1)>>1;
if(check(l,mid)) L=mid;
else R=mid-1;
}
ans[id]=s-(L-l+1);
}
else if(id>1&&l>ls[id]) upd(ls[id],-1),upd(l,1),ls[id]=l;
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}

浙公网安备 33010602011771号