BZOJ3277=BZOJ3473串题解(广义SAM)

题目:BZOJ3277=BZOJ3473.
题目大意:给定 n n n个串 S i S_i Si,对于每一个串 S i S_i Si,求它有多少个子串(不需要本质不同,一开始以为要求本质不同迷了半天)是其它至少 k k k个串的子串.
1 ≤ n , k , ∣ S ∣ ≤ 1 0 5 1\leq n,k,|S|\leq 10^5 1n,k,S105.

首先看到子串先建立SAM,由于多个串建广义SAM.

很容易发现,若一个状态 i i i在一个串中出现,那么 i i i在parent树上的所有祖先都会在这个串当中出现.这让我们想到了一个看起来比较暴力的做法,给每个状态一个标记 i d id id表示这个串最后一次是被哪个串经过.然后拿当前串往SAM里跑一遍,每到一个状态 i i i,则把 i d [ i ] id[i] id[i]赋值成当前串的编号,并大力跳parent指针把 i i i的祖先的 i d id id也赋值成当前串的编号直到跳到一个点满足它的 i d id id就是当前串的编号或无法再跳.

最后求答案前先预处理 s u m sum sum数组表示每一个状态到parent树上的根这条链上满足 c n t ≥ k cnt\geq k cntk的所有点的 m a x − m i n + 1 max-min+1 maxmin+1之和,由于不要求子串本质不同,所以直接把串放上去跑并把所有经过点的 s u m sum sum之和即可.

看起来这个做法的复杂度是 O ( ∣ S ∣ 2 ) O(|S|^2) O(S2)的,其实我们可以证明它是 O ( ∣ S ∣ ∣ S ∣ ) O(|S|\sqrt{|S|}) O(SS )的.

一个长度为 n n n的串放到SAM上运行时,第 i i i个字符的在parent树上深度最多为 i i i,也就是说它最多只有 i i i个祖先,那么这个字符串最多使用到的节点数为:
O ( ∑ i = 1 n i ) = O ( n ( n + 1 ) 2 ) = O ( n 2 ) O(\sum_{i=1}^{n}i)=O(\frac{n(n+1)}{2})=O(n^2) O(i=1ni)=O(2n(n+1))=O(n2)

而这个串能够使用的状态数不会超过SAM的结点个数,所以一个长度为 n n n串的最多会使用 O ( m i n ( ∣ S ∣ , n 2 ) ) O(min(|S|,n^2)) O(min(S,n2))的状态.

根据均值不等式, n = ∣ S ∣ n=\sqrt{|S|} n=S 的时候时间复杂度最高,也就是说时间复杂度是 O ( n n ) O(n\sqrt{n}) O(nn )的.

那么算法总时间复杂度为 O ( ∣ S ∣ ( ∣ S ∣ + Σ ) ) O(|S|(\sqrt{|S|}+\Sigma)) O(S(S +Σ)).

值得注意的是,在构造后缀自动机的时候,新建的虚点的 i d id id和在多少个串中出现过的指标 c n t cnt cnt应该要从原来的点那里复制过来.

代码如下:

#include<bits/stdc++.h> 
  using namespace std;
 
#define Abigail inline void
typedef long long LL;

const int N=100000,C=26;

int n,k;
struct automaton{
  int s[C],len,par,cnt,id;
}tr[N*2+9];
int cn,last,now;

void Build_sam(){cn=last=1;now=0;}
void reBuild_sam(){last=1;++now;}

void extend(int x){
  int p=last;
  if (tr[p].s[x]){
    int q=tr[p].s[x];
    if (tr[p].len+1==tr[q].len) last=q;
	else {
	  tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
	  tr[q].par=cn;
	  while (p&&tr[p].s[x]==q) tr[p].s[x]=cn,p=tr[p].par;
	  last=cn;
	}
  }else{
  	int np=++cn;
  	tr[np].len=tr[p].len+1;
  	last=np;
  	while (p&&!tr[p].s[x]) tr[p].s[x]=np,p=tr[p].par;
  	if (!p) tr[np].par=1;
  	else {
      int q=tr[p].s[x];
      if (tr[p].len+1==tr[q].len) tr[np].par=q;
      else {
      	tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
      	tr[q].par=tr[np].par=cn;
      	while (p&&tr[p].s[x]==q) tr[p].s[x]=cn,p=tr[p].par;
	  }
	}
  }
  for (p=last;p&&tr[p].id^now;p=tr[p].par)
    tr[p].id=now,++tr[p].cnt;
}

struct side{
  int y,next;
}e[N*2+9];
int lin[N*2+9],top;

void ins(int x,int y){
  e[++top].y=y;
  e[top].next=lin[x];
  lin[x]=top;
}

void Build_parent(){
  for (int i=2;i<=cn;++i)
    ins(tr[i].par,i);
}

int ans,sum[N*2+9];
string s[N+9];

void dfs(int x){
  if (tr[x].cnt>=k) sum[x]+=tr[x].len-tr[tr[x].par].len;
  sum[x]+=sum[tr[x].par];
  for (int i=lin[x];i;i=e[i].next)
    dfs(e[i].y);
}

Abigail into(){
  int len;
  scanf("%d%d",&n,&k);
  Build_sam();
  for (int i=1;i<=n;++i){
  	cin>>s[i];
  	len=s[i].size();
  	reBuild_sam();
  	for (int j=0;j<len;++j)
  	  extend(s[i][j]-'a');
  }
}

Abigail work(){
  Build_parent();
  dfs(1);
}

Abigail outo(){
  int len,np;
  for (int i=1;i<=n;++i){
    len=s[i].size();
	np=1;ans=0;
    for (int j=0;j<len;++j){
	  np=tr[np].s[s[i][j]-'a'];
	  ans+=sum[np];
	}
	printf("%d ",ans);
  }
  puts("");
}

int main(){
  into();
  work();
  outo();
  return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值