[LOJ 2720][BZOJ 5417][UOJ 395][NOI 2018]你的名字

[LOJ 2720][BZOJ 5417][UOJ 395][NOI 2018]你的名字

题意

给定一个大串 \(S\) 以及 \(q\) 次询问, 每次询问给定一个串 \(T\) 和区间 \([l,r]\), 求 \(T\) 中有多少本质不同的子串不是 \(S[l:r]\) 的子串.

\(|S|\le 5\times 10^5,q\le 10^5,\sum|T|\le10^6\).

题解

普通的码农字符串题...

获得成就: \(40\texttt{min}(2400\texttt{s})\) 内打完 \(3.9\texttt{kB}\) 的代码(然而并没有打完就A...还是太菜了...)

感觉考场上如果T1T2没打满的话写个 \(68\) 分沙雕SAM暴力(询问区间都是原串的 \(17\) 个测试点)就可以跑路了...分高好写还不用调...

个人的大体思路是: 因为求本质不同子串个数是容易的, 所以先补集转化为求 \(T\) 的所有本质不同的子串中是 \(S[l:r]\) 的串的个数.

按照套路我们维护一个类似扫描线的东西, 用SAM对 \(T\) 的所有下标 \(i\) 求出以 \(i\) 为右端点且是 \(S[l:r]\) 的子串的最长子串长度. 按照SAM的套路, 这部分的计算就是直接用 \(T\)\(S\) 的SAM上面跑, 如果可以匹配就匹配, 不能匹配跳到后缀自动机的父亲节点上来移动左端点.

按照上面这样计算是对整串来说的. 因为还要考虑区间 \([l,r]\) 的事情, 我们用线段树合并维护出每个结点的right集合, 设当前匹配长度为 \(len\), 那么只有当当前状态的right集合与 \([l+len-1,r]\) 有交集才说明与 \(S[l:r]\) 匹配. 如果不满足这个条件, 不能按照SAM的普通套路直接跳prt, 而是应该让 \(len\) 减少 \(1\). 直接跳的话左端点会移动若干个位置, 可能会跳过最优长度. SAM普通套路直接跳prt是因为如果 \(len\) 只减少 \(1\) 而没有到达prt的长度的话依然没有改变当前状态不能匹配的事实.

然而直接这样计算铁定会有重复, 我们对 \(T\) 的反串建SA求出所有前缀的最长公共后缀长度作为去重的参考信息. 按照SA求本质不同子串个数的套路, 重复的子串必然出现在后缀数组上相邻的两个后缀(注意是反串的后缀)上. 假设相邻的两个后缀 \(i,j\) 的在原串的对应前缀的最大匹配长度分别是 \(mlen_i,mlen_j\) 且它们的LCP是 \(height\) 的话, 贡献就是 \(mlen_j-\min(height,mlen_i)\).

实际上 \(mlen_j\) 肯定不会小于 \(\min(height,mlen_i)\), 因为这部分串是完全一样的. 所以直接减就行了.

以前用map的写法被UOJ 64位指针debuff给卡内存了qaq...然而指针线段树依然在被卡内存...UOJ变97分了QAQ

参考代码

之前听说今天minusT要更新Subterranean Rose? 完蛋在学校看不了qaq

#include <bits/stdc++.h>

const int MAXN=1e6+10;
typedef long long intEx;

struct Node{
	int l;
	int r;
	int sum;
	Node* lch;
	Node* rch;
	Node(int,int);
	void Insert(int);
	int Query(int,int);
};
Node* N[MAXN];

int n;
int q;
int cnt=1;
int root=1;
int last=1;
int s[MAXN];
int SA[MAXN];
int len[MAXN];
int prt[MAXN];
int buc[MAXN];
int mlen[MAXN];
char buf[MAXN];
int rank[MAXN];
int height[MAXN];
int chd[MAXN][26];
int* x=new int[MAXN];
int* y=new int[MAXN];

void BuildSAM();
void Extend(char);
void BuildSA(char*,int);
Node* Merge(Node*,Node*);

int main(){
	freopen("name.in","r",stdin);
	freopen("name.out","w",stdout);
	scanf("%s",buf+1);
	n=strlen(buf+1);
	for(int i=1;i<=n;i++)
		Extend(buf[i]);
	BuildSAM();
	scanf("%d",&q);
	while(q--){
		int l,r;
		scanf("%s",buf+1);
		scanf("%d%d",&l,&r);
		int m=strlen(buf+1);
		int cur=root,curlen=0;
		for(int i=1;i<=m;i++){
			int x=buf[i]-'a';
			while(cur!=root&&!chd[cur][x]){
				cur=prt[cur];
				curlen=len[cur];
			}
			if(chd[cur][x]){
				++curlen;
				cur=chd[cur][x];
				while(cur!=root&&!N[cur]->Query(l+curlen-1,r)){
					--curlen;
					if(curlen<=len[prt[cur]])
						cur=prt[cur];
				}
			}
			mlen[i]=curlen;
		}
		std::reverse(buf+1,buf+m+1);
		BuildSA(buf,m);
		intEx ans=0;
		int last=0;
		for(int i=1;i<=m;i++){
			ans+=(m-SA[i]+1)-height[i];
			last=std::min(height[i],last);
			ans-=mlen[m-SA[i]+1]-last;
			last=mlen[m-SA[i]+1];
		}
		printf("%lld\n",ans);
	}
	return 0;
}

void BuildSAM(){
	memset(buc,0,sizeof(int)*(n+1));
	for(int i=1;i<=cnt;i++)
		++buc[len[i]];
	for(int i=1;i<=n;i++)
		buc[i]+=buc[i-1];
	for(int i=cnt;i>=1;i--)
		s[buc[len[i]]--]=i;
	for(int i=cnt;i>=1;i--)
		N[prt[s[i]]]=Merge(N[prt[s[i]]],N[s[i]]);
}

void Extend(char ch){
	int p=last;
	int x=ch-'a';
	int np=++cnt;
	last=np;
	len[np]=len[p]+1;
	N[np]=new Node(1,n);
	N[np]->Insert(len[np]);
	while(p&&!chd[p][x])
		chd[p][x]=np,p=prt[p];
	if(!p)
		prt[np]=root;
	else{
		int q=chd[p][x];
		if(len[q]==len[p]+1)
			prt[np]=q;
		else{
			int nq=++cnt;
			memcpy(chd[nq],chd[q],sizeof(chd[q]));
			N[nq]=new Node(1,n);
			len[nq]=len[p]+1;
			prt[nq]=prt[q];
			prt[q]=nq;
			prt[np]=nq;
			while(p&&chd[p][x]==q)
				chd[p][x]=nq,p=prt[p];
		}
	}
}

void Node::Insert(int x){
	++this->sum;
	if(this->l!=this->r){
		int mid=(this->l+this->r)>>1;
		if(x<=mid){
			if(this->lch==NULL)
				this->lch=new Node(this->l,mid);
			this->lch->Insert(x);
		}
		else{
			if(this->rch==NULL)
				this->rch=new Node(mid+1,this->r);
			this->rch->Insert(x);
		}
	}
}

int Node::Query(int l,int r){
	if(l<=this->l&&this->r<=r)
		return this->sum;
	else{
		int ans=0;
		int mid=(this->l+this->r)>>1;
		if(l<=mid&&this->lch)
			ans+=this->lch->Query(l,r);
		if(mid+1<=r&&this->rch)
			ans+=this->rch->Query(l,r);
		return ans;
	}
}

Node* Merge(Node* a,Node* b){
	if(a==NULL)
		return b;
	if(b==NULL)
		return a;
	Node* N=new Node(a->l,b->r);
	N->sum=a->sum+b->sum;
	N->lch=Merge(a->lch,b->lch);
	N->rch=Merge(a->rch,b->rch);
	return N;
}

void BuildSA(char* s,int n){
	int m=127;
	memset(buc+1,0,sizeof(int)*m);
	for(int i=1;i<=n;i++)
		++buc[x[i]=s[i]];
	for(int i=1;i<=m;i++)
		buc[i]+=buc[i-1];
	for(int i=n;i>=1;i--)
		SA[buc[x[i]]--]=i;
	for(int k=1;k<n;k<<=1){
		int p=0;
		for(int i=n-k+1;i<=n;i++)
			y[++p]=i;
		for(int i=1;i<=n;i++)
			if(SA[i]>k)
				y[++p]=SA[i]-k;
		memset(buc+1,0,sizeof(int)*m);
		for(int i=1;i<=n;i++)
			++buc[x[i]];
		for(int i=1;i<=m;i++)
			buc[i]+=buc[i-1];
		for(int i=n;i>=1;i--)
			SA[buc[x[y[i]]]--]=y[i];
		std::swap(x,y);
		x[SA[1]]=1;
		p=1;
		for(int i=2;i<=n;i++)
			x[SA[i]]=(y[SA[i]]==y[SA[i-1]]&&y[SA[i]+k]==y[SA[i-1]+k])?p:++p;
		if(p>=n)
			break;
		m=p;
	}
	for(int i=1;i<=n;i++)
		rank[SA[i]]=i;
	int k=0;
	for(int i=1;i<=n;i++){
		if(rank[i]==1)
			continue;
		if(k)
			--k;
		int j=SA[rank[i]-1];
		while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])
			++k;
		height[rank[i]]=k;
	}
}

Node::Node(int l,int r):l(l),r(r),sum(0),lch(NULL),rch(NULL){}

posted @ 2019-04-27 19:47  rvalue  阅读(264)  评论(0编辑  收藏  举报