【后缀自动机-后缀树上的维护】hdu4641

23 篇文章 0 订阅
13 篇文章 0 订阅

每次给字符串加一个字符,并询问当时出现至少k次的子串有多少

貌似数据特别水,看网上题解都是暴力过的,所以自然要给出一个不会超时的算法

当初考的时候,觉得很难维护,因为每次相当于在后缀树上拆一条边,并加点进去,并维护当根路径上的值,好像只能动态树之类的,即便离线,貌似也要用树链剖分,因此当时就没管了

回过头来看,其实还是挺好维护的,甚至只要离线在dfs序上处理就可以了

考虑先把后缀树建好,然后每次操作在后缀自动机上走去修改后缀树上的值。考虑这样离线做的合法性,每次走到一个节点,去把他的祖先们全部加1,首先这些祖先所代表的字符串都是当前串的后缀,因此一定是之前出现过的,然后,自己这个节点所能接受的所有子串是(len[rt[i]],len[i]],而我们走到这个节点所经过的长度也正好是len[i],所以这个节点所能接受的所有子串都是需要加1的,因此这个做法正确性是有保障的

然后考虑怎么好的实现,我们可以考虑用一些指针指出最上的不被接受的节点是哪些,显然随着插入操作的增加,这些指针会单调往下走,均摊就是线性的,而且修改的时候也只会影响到一个指针,考虑怎样快速找到一个指针,这个操作我们可以在dfs序上实现,每次分出一个指针,相当于在一段区间上覆盖上一个值,因此就是一个区间修改,单点查询的线段树,然后每次我们要知道又要询问其权值是否大于等于k才能考虑是否下放指针,这个又可以在dfs序上用一个单点修改,区间查询的线段树实现

复杂度o(nlogn)

线段树部分用zkw线段树写出来十分简短

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int next[500000][26];
int net[2000000],sora[2000000];
int rt[500000],l[500000],op[500000],ed[500000],tail[500000];
char ch[500000];
long long ans;
int n,m,ss,s1,cm,k,m1,w_time,top;
int b[1048576],t[1048576],s[1048576];
int q[500000];
void suffix_sam(int &last,int chr)
{
	int x,y;
	s1++,l[s1]=l[last]+1;
	for (x=last,last=s1;x && (!next[x][chr]);x=rt[x]) next[x][chr]=s1;
	y=next[x][chr];
	if (!y) next[x][chr]=s1,rt[s1]=0;
	else if (l[x]+1==l[y]) rt[s1]=y;
	else {
		s1++,l[s1]=l[x]+1;
		for (int j=0;j<='z'-'a';j++) next[s1][j]=next[y][j];
		rt[s1]=rt[y],rt[y]=s1,rt[last]=s1;
		for (;x && (next[x][chr]==y);x=rt[x]) next[x][chr]=s1;
		if (next[x][chr]==y) next[x][chr]=s1;
	}
}
void origin()
{
	for (int i=0;i<=s1;i++) {
		for (int j=0;j<='z'-'a';j++)
			next[i][j]=0;
	}
	for (int i=1;i<=m1+m1;i++) b[i]=t[i]=s[i]=0;
	s1=0,ss=0,m1=0;
}
void link(int x,int y)
{
	++ss,net[tail[x]]=ss,tail[x]=ss,sora[ss]=y,net[ss]=0;
}
void dfs(int s)
{
	++top,op[s]=top;
	for (int i=s,ne;net[i];) {
		i=net[i],ne=sora[i];
		dfs(ne);
	}
	ed[s]=top;
}
void change(int l,int r,int x)
{
	++w_time;
	l+=m1-1,r+=m1+1;
	for (;!((l^r)==1);l>>=1,r>>=1) {
		if ((l&1)==0) b[l+1]=x,t[l+1]=w_time;
		if ((r&1)==1) b[r-1]=x,t[r-1]=w_time;
	}
}
int ask(int x)
{
	int b1=0,t1=0;
	for (x+=m1;x;x>>=1)
		if (t[x]>t1) b1=b[x],t1=t[x];
	return b1;
}
void modify(int x)
{
	for (x+=m1;x;x>>=1) s[x]++;
}
int query(int l,int r)
{
	int sum=0;
	l+=m1-1,r+=m1+1;
	for (;!((l^r)==1);l>>=1,r>>=1) {
		if ((l&1)==0) sum+=s[l+1];
		if ((r&1)==1) sum+=s[r-1];
	}
	return sum;
}
void pushdown(int x)
{
	ans+=l[x]-l[rt[x]];
	for (int i=x,ne;net[i];) {
		i=net[i],ne=sora[i];
		int cnt=query(op[ne],ed[ne]);
		if (cnt>=k) pushdown(ne);
		else change(op[ne],ed[ne],ne);
	}
}
void doit(int x)
{
	modify(op[x]);
	int y=ask(op[x]);
	int cnt=query(op[y],ed[y]);
	if (cnt>=k) pushdown(y);
}
char c[20];
int main()
{
	for (;scanf("%d%d%d",&n,&m,&k)==3;) {
		origin();
		scanf("%s",ch+1);
		cm=0;
		for (int i=1;i<=m;i++) {
			scanf("%d",&q[i]);
			if (q[i]==1) {
				scanf("%s",c+1);
				++cm;
				ch[cm+n]=c[1];
			}
		}
		int last=0;
		for (int i=1;i<=n+cm;i++) suffix_sam(last,ch[i]-'a');
		ss=s1;
		for (int i=0;i<=s1;i++) tail[i]=i,net[i]=0;
		for (m1=1;m1<=s1+2;m1<<=1) ;
		for (int i=1;i<=s1;i++) {
			link(rt[i],i);
		}
		for (int i=0;i<=s1;i++) op[i]=ed[i]=0;
		top=0;
		dfs(0);
		int s=0;
		ans=0;
		w_time=0;
		for (int i=1;i<=n;i++) {
			s=next[s][ch[i]-'a'];
			doit(s);
		}
		cm=0;
		for (int i=1;i<=m;i++) {
			if (q[i]==2) {
				printf("%lld\n",ans);
			}
			else {
				cm++;
				s=next[s][ch[n+cm]-'a'];
				doit(s);
			}
		}
	}
	return 0;
} 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值