Loading

后缀数组题目选讲

Codeforces 1654F Minimal String Xoration

\(T_k\) 表示 \(s_{0\oplus k}s_{1\oplus k}s_{2 \oplus_k}\cdots s_{(2^{n}-1)\oplus k}\)。设 \(R(k,t)\) 表示 \(T_k\) 的前 \(2^t\) 位,\(f(k,t)\) 表示 \(R(k,t)\) 在所有 \(R(,t)\) 的排名。

注意到 \(R(k,t+1) = R(k,t) + R(k \oplus 2^{t},t)\),因此可以和 SA 一样倍增排序。

# include <bits/stdc++.h>
const int N=300010,INF=0x3f3f3f3f;
inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-') f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}
char s[N];
struct Node{
	int fir,sec,id;
	bool operator < (const Node &rhs) const{
		return (fir!=rhs.fir)?(fir<rhs.fir):(sec<rhs.sec);
	}
	bool operator != (const Node &rhs) const{
		return (*this<rhs)||(rhs<*this);
	}
}g[N];
int rk[N];
int n;
int main(void){
	n=1<<read();
	scanf("%s",s);		
	for(int i=0;i<n;++i) g[i]=(Node){s[i],0,i};
	std::sort(g,g+n);
	for(int i=0,cc=0;i<n;++i){
		if(i==0||g[i]!=g[i-1]) rk[g[i].id]=++cc;
		else rk[g[i].id]=cc;
	}
	for(int w=1;w<n;w<<=1){
		for(int i=0;i<n;++i) g[i]=(Node){rk[i],rk[i^w],i};
		std::sort(g,g+n);
		for(int i=0,cc=0;i<n;++i){
			if(i==0||g[i]!=g[i-1]) rk[g[i].id]=++cc;
			else rk[g[i].id]=cc;
		}
	}
	for(int v=0;v<n;++v) if(rk[v]==1){
		for(int i=0;i<n;++i) putchar(s[i^v]);
		exit(0);
	}
	return 0;
}

Luogu P6095 [JSOI2015] 串分割

设给定的数串为 \(s\),长度为 \(n\),则答案的位数必然为 \(l = \left \lceil \frac{n}{k} \right \rceil\)。因此,考虑找出所有可能出现的长度为 \(l\) 的子串,并二分检查。

具体地,设当前检查的答案为 \(s'\),考虑枚举循环移位的起点,并依次检查。不难想到贪心:如果当前可以匹配 \(l\) 位(接下来 \(l\) 位的字典序不比 \(s'\) 大),那么就匹配 \(l\) 位,否则匹配 \(l-1\) 位。这个策略是可行的,因为如果在可以匹配 \(l\) 位时只匹配了小于 \(l\) 位,则下一次至多也只能匹配 \(l\) 位,这样一定不如直接匹配 \(l\) 位,下一次匹配 \(l-1\) 位。

最后,还是因为每一次至多只匹配 \(l\) 位,因此循环移位起点在大于 \(l\) 的位置是没有用的(相当于把最前面的一段放到最后去了),不影响检查是否通过。因此我们只枚举起点在 \([1,l]\) 的循环移位,循环内每次检查是 \(O(n/l)\) 的,因此一次检查是 \(O(n)\) 的。

实现的时候可以直接二分答案在后缀数组上的排名,判断能否匹配 \(l\) 位的依据变为起点在后缀数组中的排名是否不超过当前二分的值。如果我们二分的答案刚好是起点对应后缀的前缀,此时会误判为不能匹配。但这是不影响的,因为起点对应后缀也在后缀数组中,当我们二分到其排名时就能够正常判断了。

# include <bits/stdc++.h>

const int N=400010,INF=0x3f3f3f3f;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-') f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}

int n,k;
int s[N];

namespace SA{
	int sa[N],rk[N],ork[N],buc[N],id[N];
	int mx=128;
	
	inline void clear(void){
		mx=128;
		memset(buc,0,sizeof(buc));
		return;
	}

	
	inline void init(void){
		clear();
		for(int i=1;i<=n;++i) ++buc[rk[i]=s[i]];
		for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
		for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
		
		for(int w=1,cc=0;;mx=cc,cc=0,w<<=1){
			for(int i=n-w+1;i<=n;++i) id[++cc]=i;
			for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
			
			memset(buc,0,sizeof(buc));
			memcpy(ork,rk,sizeof(ork));
			cc=0;
			
			for(int i=1;i<=n;++i) ++buc[ork[i]];
			for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
			for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
			
			for(int i=1;i<=n;++i)
				rk[sa[i]]=((ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc);
			if(cc==n) break;
		}
		return;
	}
}
using namespace SA;
int len;

inline bool chk(int x){
	bool ok=false;
	for(int i=1;i<=len;++i){
		int mat=0;
		for(int j=1;j<=k;++j){
			int p=(i+mat-1)%(n/2)+1;
			if(rk[p]<=x) mat+=len;
			else mat+=len-1;
		}
		ok|=(mat>=(n/2));
	}
	return ok;
}

int main(void){
	n=read(),k=read();
	for(int i=1;i<=n;++i) scanf("%1d",&s[i]),s[i+n]=s[i];
	len=(n+k-1)/k;
	n*=2,init();
	
	int l=1,r=n,ans=n+1;
	while(l<=r){
		int mid=(l+r)>>1;
		if(chk(mid)) ans=mid,r=mid-1;
		else l=mid+1;
	}
	for(int i=1;i<=len;++i) printf("%d",s[sa[ans]+i-1]);

	return 0;
}

P7769 「CGOI-1」大师选徒

两个长度为 \(n_0\) 的序列 \(A,B\) 关于 \(s\) 满足条件可以转化为:

  • \(A_1 + B_1 = s\)
  • \(\forall 1 \leq i < n_0,A_{i+1} - A_i = -(B_{i+1} - B_i)\)

\(a\) 序列的向前差分数组 \(d\) 以及 \(-d\)\(d\) 中元素变为其相反数)拼接到一起,求出其后缀数组。询问时,找到 \(d_l\) 所在位置 \(pos\),二分求出后缀数组上与后缀 \(pos\) LCP 大于等于 \(r-l\) 的区间。如果这个区间中,有来源于 \(-d\) 的位置,并且该位置在 \(a\) 数组上对应的值恰为 \(a_l - s\),那么就找到了一组合法解。给每个 \(a_i\) 开一个 vector,对于后缀数组中每个来源于 \(-d\) 的位置,将其位置 push 进对应的 vector 中。查询时二分即可。

注意判掉平凡的无解情况。

# include <bits/stdc++.h>

const int N=800010,INF=0x3f3f3f3f;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-') f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}

int n,q;
char s[N];

int buc[N],sa[N],rk[N],ht[N],id[N],ork[N],mx;
int ans[N];

int mn[N][20];
int a[N],d[N];

std::vector <int> bc[N];

inline void init_sa(int n){
	mx=n+2;
	std::fill(buc+1,buc+1+mx,0);
	for(int i=1;i<=n;++i) ++buc[rk[i]=d[i]+(::n+1)];
	for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
	for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
	for(int w=1,cc=0;w<=n;w<<=1,mx=cc,cc=0){
		for(int i=n-w+1;i<=n;++i) id[++cc]=i;
		for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
		memset(buc,0,(mx+1)*4);
		memcpy(ork,rk,sizeof(ork));
		for(int i=1;i<=n;++i) ++buc[ork[i]];
		for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
		for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
		cc=0;
		for(int i=1;i<=n;++i)
			rk[sa[i]]=(ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc;
		if(cc==n) break;
	}
	for(int i=1,k=0;i<=n;++i){
		if(k) --k;
		while(d[i+k]==d[sa[rk[i]-1]+k]) ++k;
		ht[rk[i]]=mn[rk[i]][0]=k;
	}
	for(int j=1;(1<<j)<=n;++j) for(int i=1;i+(1<<j)-1<=n;++i) 
		mn[i][j]=std::min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
	for(int i=1;i<=n;++i){
		if(sa[i]>::n) bc[a[sa[i]-::n]].push_back(i);
	}
	return;
}
inline int lcp(int i,int j){
	if(i>j) std::swap(i,j);
	if(i==j) return 2*n-sa[i]+1;
	int l=std::__lg(j-(i++));
	int ans=std::min(mn[i][l],mn[j-(1<<l)+1][l]);
	return ans;
}

inline int getpre(int x,int len){
	int l=1,r=x,ans=x;
	while(l<=r){
		int mid=(l+r)>>1;
		if(lcp(mid,x)>=len) ans=mid,r=mid-1;
		else l=mid+1;
	}
	return ans;
}
inline int getnex(int x,int len){
	int l=x,r=2*n,ans=x;
	while(l<=r){
		int mid=(l+r)>>1;
		if(lcp(mid,x)>=len) ans=mid,l=mid+1;
		else r=mid-1;
	}
	return ans;
}

int main(void){
	n=read(),q=read();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<=n;++i) d[i]=a[i+1]-a[i],d[i+n]=-d[i];
	init_sa(2*n);
	
	while(q--){
		int s=read(),l=read(),r=read();
		int len=r-l,dt=s-a[l];
		if(dt<=0||dt>n||!bc[dt].size()){
			puts("No");
			continue;
		}
		if(l==r){
			puts("Yes");
			continue;
		}
		int L=getpre(rk[l],len),R=getnex(rk[l],len);
		auto itl=std::lower_bound(bc[dt].begin(),bc[dt].end(),L),
			 itr=std::upper_bound(bc[dt].begin(),bc[dt].end(),R);
		puts(itl==itr?"No":"Yes");
	}

	return 0;
}

Luogu P7361「JZOI-1」拜神

类似品酒大会的思路。枚举长度 \(L=n-1,\cdots,0\),并对于每个 \(p\),维护出位置 \(p\) 左侧最靠右的位置 \(le(p)\) 使得后缀 \(p\) 和后缀 \(le(p)\) 的 LCP 大于等于 \(L\)。这可以使用并查集 + 启发式合并来实现。具体地,将集合 \(S\)\(T\) 合并时,设我们把所有 \(v \in S\) 插入到 \(T\) 中,那么每插入一个 \(v\),就在原来的 \(T\)二分找到 \(v\) 的前驱 \(pre\) 和后继 \(nex\),并修改 \(le(v) = pre,le(nex) = v\)。那么对于长度 \(L\) 而言,只要询问区间 \([l,r]\) 中存在一个大于等于 \(l\) 的数,\(L\) 就合法。

使用可持久化线段树维护出对于每个 \(L\)\(le\) 数组。查询时二分即可。

# include <bits/stdc++.h>

const int N=50010,INF=0x3f3f3f3f;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-') f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}

int n,q;
char s[N];

int buc[N],sa[N],rk[N],ht[N],id[N],ork[N],mx;
int ans[N];

inline void init_sa(void){
	mx=128;
	std::fill(buc+1,buc+1+mx,0);
	for(int i=1;i<=n;++i) ++buc[rk[i]=s[i]];
	for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
	for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
	for(int w=1,cc=0;w<=n;w<<=1,mx=cc,cc=0){
		for(int i=n-w+1;i<=n;++i) id[++cc]=i;
		for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
		memset(buc,0,(mx+1)*4);
		memcpy(ork,rk,sizeof(ork));
		for(int i=1;i<=n;++i) ++buc[ork[i]];
		for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
		for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
		cc=0;
		for(int i=1;i<=n;++i)
			rk[sa[i]]=(ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc;
		if(cc==n) break;
	}
	for(int i=1,k=0;i<=n;++i){
		if(k) --k;
		while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
		ht[rk[i]]=k;
	}
	return;
}

struct Node{
	int x,w;
	bool operator < (const Node &rhs) const{
		return w>rhs.w;
	}
};
std::vector <Node> S;

namespace SGT{
	struct Node{
		int mx,lc,rc;
		Node(){mx=INF,lc=rc=0;}
	}tr[N*500];
	int cnt;
	
	inline int& lc(int x){
		return tr[x].lc;
	}
	inline int& rc(int x){
		return tr[x].rc;
	}
	inline void psup(int k){
		return tr[k].mx=std::min(tr[lc(k)].mx,tr[rc(k)].mx),void();
	}
	void update(int &k,int lst,int l,int r,int x,int v){
		k=++cnt,tr[k]=tr[lst];
		if(l==r) return tr[k].mx=v,void();
		int mid=(l+r)>>1;
		if(x<=mid) update(lc(k),lc(lst),l,mid,x,v);
		else update(rc(k),rc(lst),mid+1,r,x,v);
		psup(k);
		return;
	}
	int query(int k,int l,int r,int L,int R){
		if(!k) return INF;
		if(L<=l&&r<=R) return tr[k].mx;
		int mid=(l+r)>>1,ans=INF;
		if(L<=mid) ans=query(lc(k),l,mid,L,R);
		if(mid<R) ans=std::min(ans,query(rc(k),mid+1,r,L,R));
		return ans;
	}
}

std::set <int> st[N];
int f[N];

inline int find(int x){
	return (f[x]==x)?x:f[x]=find(f[x]);
}
int rt[N];

inline void merge(int u,int v,int ver){	
	u=find(u),v=find(v);
	if(u==v) return;
	if(st[u].size()>st[v].size()) std::swap(u,v);
	while(st[u].size()){
		int w=*st[u].begin();
		auto it=st[v].lower_bound(w);
		if(it!=st[v].end()) SGT::update(rt[ver],rt[ver],1,n,w,*it);
		if(it!=st[v].begin()) --it,SGT::update(rt[ver],rt[ver],1,n,*it,w);
		st[u].erase(w),st[v].insert(w);
	}
	f[u]=v;
	return;
}

int main(void){	
	n=read(),q=read();
	scanf("%s",s+1);
	init_sa();
	
	for(int i=2;i<=n;++i) S.emplace_back((Node){i,ht[i]});
	std::sort(S.begin(),S.end());
	auto it=S.begin();
	
	for(int i=1;i<=n;++i) f[i]=i,st[i].insert(i);

	for(int L=n-1;L>=0;--L){
		rt[L]=rt[L+1];
		while(it!=S.end()&&(*it).w==L){
			int p=(*it).x;
			merge(sa[p-1],sa[p],L);
			++it;
		}
	}
	
	while(q--){
		int L=read(),R=read();
		int l=1,r=R-L+1,ans=0;
		while(l<=r){
			int mid=(l+r)>>1;			
			if(SGT::query(rt[mid],1,n,L,R-mid)<=R-mid+1) ans=mid,l=mid+1;
			else r=mid-1;
		}
		printf("%d\n",ans);
	}
	
	return 0;
}

Codeforces 822E Liar

\(f(i,j)\) 表示只用 \(s[1,i]\) 的字符,用了 \(j\) 段,最多能匹配到 \(t\) 的哪里。

我们发现,如果我们新开了一段,能匹配就匹配显然是不劣的,于是记 \(f(i,j) = x,\text{lcp}(t[x+1,|t|],s[i+1,|s|]) = k\),则 \(f(i,j)\)\(f(i+k,j+1)\) 转移。同时也可以不选,于是 \(f(i,j)\) 也可以向 \(f(i+1,j)\) 转移。

# include <bits/stdc++.h>

const int N=200010,INF=0x3f3f3f3f;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-') f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}

int n,m,lim;

char S[N],T[N];
char s[N];
int len;
int f[N][35];

int ork[N],rk[N],buc[N],sa[N],id[N],mx=128;
int ht[N];

int mn[N][20];

inline void init(void){
	for(int i=1;i<=len;++i) ++buc[rk[i]=s[i]];
	for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
	for(int i=1;i<=len;++i) sa[buc[rk[i]]--]=i;
	
	for(int w=1,cc=0;w<=len;w<<=1,mx=cc,cc=0){
		for(int i=len-w+1;i<=len;++i) id[++cc]=i;
		for(int i=1;i<=len;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
		
		memset(buc,0,sizeof(buc)),memcpy(ork,rk,sizeof(ork)),cc=0;
		for(int i=1;i<=len;++i) ++buc[ork[i]];
		for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
		for(int i=len;i;--i) sa[buc[ork[id[i]]]--]=id[i];
		
		for(int i=1;i<=len;++i)
			rk[sa[i]]=((ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:(++cc));
		
		if(cc==len) break;
	}
	
	for(int i=1,k=0;i<=len;++i){
		if(k) --k;
		while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
		ht[rk[i]]=mn[rk[i]][0]=k;
	}
	for(int k=1;(1<<k)<=len;++k) for(int i=1;i<=len;++i)
		mn[i][k]=std::min(mn[i][k-1],mn[i+(1<<(k-1))][k-1]);
	return;
}
inline int lcp(int i,int j){
	assert(i!=j),i=rk[i],j=rk[j];
	if(i>j) std::swap(i,j);
	int k=std::__lg(j-(i++));
	return std::min(mn[i][k],mn[j-(1<<k)+1][k]);
}

int main(void){
	n=read(),scanf("%s",S+1),m=read(),scanf("%s",T+1),lim=read();
	for(int i=1;i<=n;++i) s[++len]=S[i];
	s[++len]='@';
	for(int i=1;i<=m;++i) s[++len]=T[i];
	init();
	for(int i=0;i<n;++i) for(int k=0;k<=lim;++k){
		f[i+1][k]=std::max(f[i+1][k],f[i][k]);
		int lp=lcp(i+1,(n+1)+f[i][k]+1);
		f[i+lp][k+1]=std::max(f[i+lp][k+1],f[i][k]+lp);
	}
	int ans=0;
	for(int i=0;i<=lim;++i) ans=std::max(f[n][i],ans);
	printf(ans==m?"YES":"NO");
	return 0;
}

P5284 [十二省联考 2019] 字符串问题

最后的要求形似一条从 \(A\) 串到 \(A\) 串的路径。

考虑对于一对支配关系 \(A_i,B_j\),哪些 \(A_k = S(l_k,r_k)\) 可以通过这对支配关系接到 \(A_i\) 后面。设 \(B_j = S(l_j,r_j)\),那么 \(\text{lcp}(S[l_j,|S|],S[l_k,|S|]) \geq |B_j|\) 必须成立,同时 \(|A_k| \geq |B_j|\) 也要成立。

对于第一个条件,可以二分找到后缀数组上的合法区间,只有 \(l_k\) 在这个区间中时,\(A_k\) 才可能合法。对于第二个条件,似乎没有什么头绪。考虑使用主席树优化建图。具体地,从大到小枚举 \(L\),将 \(L = |A_k|\)\(A_k\) 插入到 \(sa[l_k]\) 这个位置上,并令虚点连向 \(A_k\) 的边权值为 \(|A_k|\)。对于所有 \(L = |B_j|\)\(B_j\),让 \(B_j\) 和主席树上对应区间连边。主席树上的边权值均为 \(0\)

最后,对于每对支配关系 \(A_i,B_j\),将 \(A_i,B_j\) 连边。

答案为图中最长路。不难发现,图上没有零权环,因此有环必为正权环,此时答案为 \(+\infty\)。使用 Tarjan 判环 即可。

若无环则拓扑排序求出 DAG 上最长路,即可求得答案。

P5341 [TJOI2019] 甲苯先生和大中锋的字符串

我们可以对于每种长度,求出有多少个该长度的子串出现了恰 \(k\) 次。枚举后缀数组上每个区间 \([i,i+k-1]\),并钦定子串的 \(k\) 次出现均出现在该区间中,在此条件下,计算有多少子串出现了 \(k\) 次。

事实上,该子串长度必然不会超过该区间后缀的 LCP 长度,否则其出现了小于 \(k\) 次。同时,该子串的长度必然大于 \(\max(\text{height}(i),\text{height}(i+k))\),否则该子串出现了大于 \(k\) 次。

单调队列维护出区间 LCP 长度(一段 height 的最小值),差分统计答案即可。

# include <bits/stdc++.h>

const int N=100010,INF=0x3f3f3f3f;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-') f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}

int n,k;

int T;

char s[N];

namespace sa{
	int sa[N],rk[N],ork[N],ht[N],buc[N],id[N];
	int mx=128;
	
	inline void clear(void){
		mx=128;
		memset(buc,0,sizeof(buc));
		return;
	}
	
	inline void init(void){
		clear();
		for(int i=1;i<=n;++i) ++buc[rk[i]=s[i]];
		for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
		for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
		
		for(int w=1,cc=0;;mx=cc,cc=0,w<<=1){
			for(int i=n-w+1;i<=n;++i) id[++cc]=i;
			for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
			
			memset(buc,0,sizeof(buc));
			memcpy(ork,rk,sizeof(ork));
			cc=0;
			
			for(int i=1;i<=n;++i) ++buc[ork[i]];
			for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
			for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
			
			for(int i=1;i<=n;++i)
				rk[sa[i]]=((ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc);
			if(cc==n) break;
		}
		
		for(int i=1,k=0;i<=n;++i){
			if(k) --k;
			while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
			ht[rk[i]]=k;
		}
		return;
	}
	
	int q[N],hd,tl;
	
	inline void ins(int x){
		while(hd<=tl&&ht[q[tl]]>=ht[x]) --tl;
		q[++tl]=x;
		return;
	}
	inline void chk(int x){ // L moved from x to x+1
		while(hd<=tl&&q[hd]<=x+1) ++hd;
		return;
	}
	
	int d[N];
	
	inline void solve(void){
		init(),hd=1,tl=0,ht[n+1]=0;	
		memset(d,0,sizeof(d));
		for(int i=1;i<k;++i) ins(i+1);
		for(int i=1;i+k-1<=n;++i){
			int r=(k==1)?n-sa[i]+1:ht[q[hd]];
			int l=std::max(ht[i],ht[i+k]);
			if(l<r) ++d[l+1],--d[r+1];
			chk(i),ins(i+k);
		}
		int mx=0;
		for(int i=1;i<=n;++i) d[i]+=d[i-1],(d[i]>=d[mx]?mx=i:mx=mx);
		printf("%d\n",(d[mx]==0)?-1:mx);
		return;
	}
}

int main(void){
	T=read();
	
	while(T--){
		scanf("%s",s+1),n=strlen(s+1),k=read();
		if(n<k){
			puts("-1");
			continue;
		}
		sa::solve(); 
		
	}
	return 0;
}

P6793 [SNOI2020] 字符串

现在找出一组 \(A,B\) 集合中串的配对,考虑如何计算代价。代价即总长度减去每对匹配串的 LCP 长度之和的两倍。

一种直观的想法是品酒大会,倒序枚举 LCP 长度,然后合并后缀数组上的区间,每次匹配两个连通块中还未匹配的串。事实上这就是对的,因为如果现在不匹配以后再匹配的话权值一定更小。

# include <bits/stdc++.h>

const int N=500010,INF=0x3f3f3f3f;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-') f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}

int n,k;
char s[N],A[N],B[N];

int buc[N],sa[N],rk[N],ht[N],id[N],ork[N],mx;
long long ans;

inline void init_sa(int n){
	mx=128;
	std::fill(buc+1,buc+1+mx,0);
	for(int i=1;i<=n;++i) ++buc[rk[i]=s[i]];
	for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
	for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
	for(int w=1,cc=0;w<=n;w<<=1,mx=cc,cc=0){
		for(int i=n-w+1;i<=n;++i) id[++cc]=i;
		for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
		memset(buc,0,(mx+1)*4);
		memcpy(ork,rk,sizeof(ork));
		for(int i=1;i<=n;++i) ++buc[ork[i]];
		for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
		for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
		cc=0;
		for(int i=1;i<=n;++i)
			rk[sa[i]]=(ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc;
		if(cc==n) break;
	}
	for(int i=1,k=0;i<=n;++i){
		if(k) --k;
		while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
		ht[rk[i]]=k;
	}
	return;
}

struct Node{
	int x,w;
	bool operator < (const Node &rhs) const{
		return w>rhs.w;
	}
};
std::vector <Node> S;

std::set <int> st[N];
int f[N];

inline int find(int x){
	return (f[x]==x)?x:f[x]=find(f[x]);
}
int ca[N],cb[N];

inline void merge(int u,int v,int ver){	
	u=find(u),v=find(v);
	if(u==v) return;
	f[u]=v;
	int l=std::min(ca[u],cb[v]),r=std::min(cb[u],ca[v]);	
	ans+=1ll*ver*(l+r),ca[u]-=l,cb[v]-=l,cb[u]-=r,ca[v]-=r,ca[v]+=ca[u],cb[v]+=cb[u];	
	return;
}

int main(void){	
	n=read(),k=read();
	scanf("%s",A+1),scanf("%s",B+1);
	for(int i=1;i<=n;++i) s[i]=A[i],s[i+(n+1)]=B[i];
	s[n+1]='#',init_sa(2*n+1);
	
	for(int i=2;i<=2*n+1;++i) S.emplace_back((Node){i,ht[i]});
	std::sort(S.begin(),S.end());
	auto it=S.begin();
	
	for(int i=1;i<=2*n+1;++i) f[i]=i;
	for(int i=1;i<=n-k+1;++i) ca[i]=1,cb[i+(n+1)]=1;

	for(int L=n;L>=0;--L){
		while(it!=S.end()&&(*it).w==L){
			int p=(*it).x;
			merge(sa[p-1],sa[p],std::min(L,k));
			++it;
		}
	}
	printf("%lld",1ll*(n-k+1)*k-ans);
	return 0;
}

Luogu P5161 WD与数列

长度为 \(1\) 的贡献有 \(\frac{n(n+1)}{2}\)。考虑长度大于 \(1\) 的串,要求即为差分序列相等。

那么,对于差分序列的两个后缀 \(i,j\),它们对答案的贡献真的是 \(|\text{lcp}(\text{suf}(i),\text{suf}(j))|\) 吗?很显然并不是,因为子串不能相交或相邻,因此对答案的贡献还要对 \(|i-j|-1\) 取 min。

那么我们可以使用品酒大会的方法(三回啊三回)干掉 \(\text{lcp}\) 的限制。设我们在 \(\text{lcp} = x\) 的时候合并了后缀集合 \(S\) 和后缀集合 \(T\),若 \(|S| > |T|\) 则交换 \(S,T\),保证启发式合并的复杂度。枚举计算 \(v \in S\) 计算 \(v\)\(T\) 集合的贡献。对于 \(T\) 集合中的元素 \(w \in T\),如果 \(|v - w| - 1 \leq x\),则贡献为 \(|v - w| - 1\),否则为 \(x\)。据此可以解出四个区间(实际上只需要线段树查询 \(2\) 次),计算每个区间内的元素数量 / 元素和,可以采用线段树合并完成。

# include <bits/stdc++.h>

const int N=300010,INF=0x3f3f3f3f;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-') f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}

typedef long long ll;

int n,q;
char s[N];

int buc[N],sa[N],rk[N],ht[N],id[N],ork[N],mx;
int d[N];

ll ans;

inline void init_sa(int n){
	mx=n;
	std::fill(buc+1,buc+1+mx,0);
	for(int i=1;i<=n;++i) ++buc[rk[i]=d[i]];
	for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
	for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
	for(int w=1,cc=0;w<=n;w<<=1,mx=cc,cc=0){
		for(int i=n-w+1;i<=n;++i) id[++cc]=i;
		for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
		memset(buc,0,(mx+1)*4);
		memcpy(ork,rk,sizeof(ork));
		for(int i=1;i<=n;++i) ++buc[ork[i]];
		for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
		for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
		cc=0;
		for(int i=1;i<=n;++i)
			rk[sa[i]]=(ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc;
		if(cc==n) break;
	}
	for(int i=1,k=0;i<=n;++i){
		if(k) --k;
		while(d[i+k]==d[sa[rk[i]-1]+k]) ++k;
		ht[rk[i]]=k;
	}
	return;
}

struct Node{
	int x,w;
	bool operator < (const Node &rhs) const{
		return w>rhs.w;
	}
};
std::vector <Node> S;

namespace SGT{
	struct Node{
		int lc,rc,cnt;
		ll sum;
		Node(){cnt=sum=lc=rc=0;}
	}tr[N*21];
	int cnt;
	
	inline int& lc(int x){
		return tr[x].lc;
	}
	inline int& rc(int x){
		return tr[x].rc;
	}
	inline void psup(Node &cur,const Node &lc,const Node &rc){
		cur.cnt=lc.cnt+rc.cnt,cur.sum=lc.sum+rc.sum;
		return;
	}
	void ins(int &k,int l,int r,int x){
		if(!k) k=++cnt;
		if(l==r) return tr[k].sum=x,tr[k].cnt=1,void();
		int mid=(l+r)>>1;
		if(x<=mid) ins(lc(k),l,mid,x);
		else ins(rc(k),mid+1,r,x);
		psup(tr[k],tr[lc(k)],tr[rc(k)]);
		return;
	}
	void merge(int &cur,int u,int v,int l,int r){
		if(!u||!v) return cur=u|v,void();
		if(l==r) return tr[cur].sum=tr[u].sum+tr[v].sum,tr[cur].cnt=tr[u].cnt+tr[v].cnt,void();
		int mid=(l+r)>>1;
		merge(lc(cur),lc(u),lc(v),l,mid);
		merge(rc(cur),rc(u),rc(v),mid+1,r);
		psup(tr[cur],tr[lc(cur)],tr[rc(cur)]);
		return;
	}
	Node query(int k,int l,int r,int L,int R){
		if(L>R||!k) return Node();
		if(L<=l&&r<=R) return tr[k];
		int mid=(l+r)>>1;
		Node res,lh,rh;
		if(L<=mid) lh=query(lc(k),l,mid,L,R);
		if(mid<R) rh=query(rc(k),mid+1,r,L,R);
		psup(res,lh,rh);
		return res;
	}
	void dfs(int k,int l,int r){
		if(l==r) return printf("(%d,%lld)",tr[k].cnt,tr[k].sum),void();
		int mid=(l+r)>>1;
		dfs(lc(k),l,mid),dfs(rc(k),mid+1,r);
		return;
	}
}

std::vector <int> st[N];
int f[N];

inline int find(int x){
	return (f[x]==x)?x:f[x]=find(f[x]);
}
int rt[N];

inline void merge(int u,int v,int ver){
	u=find(u),v=find(v);
	if(u==v) return;
	if(st[u].size()>st[v].size()) std::swap(u,v);
	
	ans+=1ll*st[u].size()*st[v].size()*ver;
	
	for(auto x:st[u]){
		int l=std::max(1,x-ver),r=x-1;
		SGT::Node res=SGT::query(rt[v],1,n,l,r);
		ans-=1ll*(ver+1-x)*res.cnt+res.sum;
		l=x+1,r=std::min(x+ver,n);
		res=SGT::query(rt[v],1,n,l,r);		
		ans-=1ll*(ver+1+x)*res.cnt-res.sum;
	}
	for(auto x:st[u]) st[v].push_back(x);
	std::vector <int> ().swap(st[u]);
	f[u]=v;
	SGT::merge(rt[v],rt[v],rt[u],1,n);
//	SGT::dfs(rt[v],1,n);
	return;
}
int tp[N];
std::map <int,int> mp;

int main(void){	
//	freopen("in.txt","r",stdin);
	
	n=read(),ans=1ll*n*(n-1)/2;
	for(int i=1;i<=n;++i) tp[i]=read();
	for(int i=1;i<n;++i) d[i]=tp[i+1]-tp[i],mp[d[i]]=1;
	int mpc=0;
	for(auto &cur:mp) cur.second=++mpc;
	for(int i=1;i<n;++i) d[i]=mp[d[i]];
	init_sa(n-1);
	for(int i=2;i<n;++i) S.emplace_back((Node){i,ht[i]});
	std::sort(S.begin(),S.end());
	auto it=S.begin();
	for(int i=1;i<n;++i) f[i]=i,st[i].push_back(i),SGT::ins(rt[i],1,n,i);
	for(int L=n-2;L>=0;--L){
		while(it!=S.end()&&(*it).w==L){
			int p=(*it).x;
			merge(sa[p-1],sa[p],L);
			++it;
		}
	}
	
	printf("%lld",ans);
	
	return 0;
}

P4094 [HEOI2016/TJOI2016]字符串

考虑 \(s[x,b](a \leq x \leq b)\)\(s[c,d]\) 的 LCP 长度,等于 \(\min\{\text{lcp}(s[x,n],s[c,n]),d-c+1,b-x+1\}\)

其中 \(d-c+1\) 是常数,可以最后考虑。重点求 \(\min\{\text{lcp}(s[x,n],s[c,n]),b-x+1\}\) 的最大值。

二分答案 \(l\)。二分找到 \(\text{lcp}(s[x,n],s[c,n]) \geq l\) 的区间,那么这段区间中大于等于 \(b-x+1\) 的数才能成为答案。主席树查询即可。

P5353 树上后缀排序

题目等价于这样一件事情:取出节点 \(x\) 到根的字符组成的序列 \(S_x\),以及根到 \(x\)节点编号组成的序列 \(I_x\)。比较 \(x,y\) 时,先比较 \(S_x,S_y\) 的字典序大小,随后比较 \(I_x,I_y\) 的字典序大小。

为了应对关键字 \(I_x\),我们在倍增排序时引入不可重排名 \(rrk\):在两个串目前相等时不再让它们的排名相等,而直接令 \(rrk[sa[i]] = i\)

现在,考虑在倍增排序时使用三个关键字:靠下半段的可重排名,靠上半段的不可重排名,靠下半段的不可重排名。

第一个是为了区分 \(S_x\) 靠下半段的字典序,第二个同时区分了 \(S_x\) 靠上半段的字典序和 \(I_x\) 靠上半段的字典序。如果比完了这个都仍然相等,那么两个串就相等了,第三关键字的作用就是区分 \(I_x\) 靠下半段的字典序。

# include <bits/stdc++.h>

const int N=500010,INF=0x3f3f3f3f;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-') f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}

typedef long long ll;

int n,q;
char s[N];

int buc[N],sa[N],rk[N],ht[N],id[N],ork[N],mx,rrk[N],bk[N];

ll ans;
int f[N][20];

inline void radix_sort(int *res,int *rk,int *id,int mx){
	std::fill(buc,buc+1+mx,0);
	for(int i=1;i<=n;++i) ++buc[rk[i]];
	for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
	for(int i=n;i;--i) res[buc[rk[id[i]]]--]=id[i];
	return;
}

inline bool equal(int x,int y,int t){
	return ork[x]==ork[y]&&ork[f[x][t]]==ork[f[y][t]];
}

inline void init_sa(int n){
	for(int i=1;i<=n;++i) rk[i]=s[i],id[i]=i;
	radix_sort(sa,rk,id,128);
	for(int i=1,cc=0;i<=n;++i){
		rk[sa[i]]=(s[sa[i]]==s[sa[i-1]]?cc:++cc);
		rrk[sa[i]]=i;
	}

	for(int w=1,t=0;w<=n;w<<=1,++t){
		for(int i=1;i<=n;++i) bk[i]=rrk[f[i][t]];
		radix_sort(ork,bk,sa,n);
		radix_sort(sa,rk,ork,n);
		std::swap(rk,ork);
		
		for(int i=1,cc=0;i<=n;++i){
			rk[sa[i]]=equal(sa[i],sa[i-1],t)?cc:++cc;
			rrk[sa[i]]=i;
			if(cc==n) return;
		}
	}
	return;
}

int main(void){
	n=read();
	for(int i=2;i<=n;++i){
		f[i][0]=read();
		for(int k=1;k<=19;++k) f[i][k]=f[f[i][k-1]][k-1];
	}
	scanf("%s",s+1);
	init_sa(n);
	for(int i=1;i<=n;++i) printf("%d ",sa[i]);
	
	return 0;
}

SP687 REPEATS - Repeats

沿用优秀的拆分一题的想法,枚举循环节长度 \(L\),每隔 \(L\) 撒一个关键点。设相邻两个关键点为 \(a,b\)。如果确有一个重复子串的相邻两次出现经过 \(a,b\) 两个关键点,那么 \(\text{lcp}(a,b)+\text{lcs}(a,b) - 1 \geq L\) 必须成立。反之,如果这条件成立,则我们在原字符串中找到了两个相等且间隔 \(L\) 的子串,它们构成它们并的 border,从而它们的并有周期 \(L\)。因此 \((\text{lcp}(a,b)+\text{lcs}(a,b) - 1)/L\) 下取整可以作为一个备选答案。

# include <bits/stdc++.h>

const int N=50010,INF=0x3f3f3f3f;

char s[N],fs[N],logt[N];
int n;

struct SuffixArray{
	int sa[N],rank[N],len,num,t[N],cnt,fir[N],sec[N],height[N],minx[N][20];
	inline void clear(void){
    	memset(sa,0,sizeof(sa)),memset(fir,0,sizeof(fir)),memset(sec,0,sizeof(sec));
	}
	inline void build(char *s,int n){
		clear();
		len=n,num=128;
		std::fill(t+1,t+1+num,0);
		for(int i=1;i<=n;++i) ++t[fir[i]=s[i]];
		for(int i=1;i<=num;++i) t[i]+=t[i-1];
		for(int i=n;i;--i) sa[t[fir[i]]--]=i;
		for(int k=1;k<=n;k<<=1){
			cnt=0;
			for(int i=n-k+1;i<=n;++i) sec[++cnt]=i;
			for(int i=1;i<=n;++i) if(sa[i]>k) sec[++cnt]=sa[i]-k;
			std::fill(t+1,t+1+num,0);
			for(int i=1;i<=n;++i) ++t[fir[i]];
			for(int i=1;i<=num;++i) t[i]+=t[i-1];
			for(int i=n;i;--i) sa[t[fir[sec[i]]]--]=sec[i];
			std::swap(sec,fir),cnt=1,fir[sa[1]]=1;
			for(int i=2;i<=n;++i)
				fir[sa[i]]=((sec[sa[i-1]]==sec[sa[i]]&&sec[sa[i-1]+k]==sec[sa[i]+k])?cnt:++cnt);
			if(cnt==n) break;
			num=cnt;
		}
		for(int i=1;i<=n;++i) rank[sa[i]]=i;
		return;
	}
	inline void get_height(char *s){
		int n=len;
		s[n+1]=s[0]=0;
		for(int i=1,k=0;i<=n;++i){
			if(k) --k;
			while(s[i+k]==s[sa[rank[i]-1]+k]) ++k;
			height[rank[i]]=k;
		}
		for(int i=1;i<=n;++i) minx[i][0]=height[i];
		for(int k=1;(1<<k)<=n;++k){
			for(int i=1;i+(1<<k)-1<=n;++i) minx[i][k]=std::min(minx[i][k-1],minx[i+(1<<(k-1))][k-1]);
		}
		return;
	}
	inline int query(int l,int r){
		int len=logt[r-l+1];
		return std::min(minx[l][len],minx[r-(1<<len)+1][len]);
	}
	inline int querylcp(int l,int r){
		if(l==0||r==0||l>n||r>n) return 0;
		int a=rank[l],b=rank[r];
		if(a>b) std::swap(a,b);
		return query(a+1,b);
	}
	
}A,B;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-')f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}
inline void solve(void){
	for(int i=1;i<=n;++i) fs[i]=s[n-i+1];
	A.build(s,n),B.build(fs,n);
	A.get_height(s),B.get_height(fs);
	int maxx=0;
	for(int len=1;len<=n;++len){
		for(int L=len,R=2*len;R<=n;L+=len,R+=len){
			int lcp=A.querylcp(L,R),lcs=B.querylcp(n-L+2,n-R+2);
			maxx=std::max(maxx,(lcp+lcs)/len+1);
		}
	}
	printf("%d\n",maxx);
	return;
}
int main(void){
	for(int i=2;i<=50000;++i) logt[i]=logt[i>>1]+1;
	int T=read();
	while(T--){
		n=read();
		char op[2];
		for(int i=1;i<=n;++i) scanf("%s",op),s[i]=op[0];
		solve();
	}

	return 0;
}

CF1073G Yet Another LCP Problem

给定字符集为小写字母的字符串 \(s\)

\(q\) 次询问,每次给定大小分别为 \(a,b\) 的集合 \(A,B \subseteq \{1,2,\cdots,n\}\),求:

\[\sum \limits_{i \in A} \sum \limits_{j \in B} \text{lcp}(\text{suf}_i,\text{suf}_j) \]

\(1 \leq n ,q \leq 2 \times 10^5, 1 \leq \sum a,\sum b \leq 2 \times 10^5\)

\(i,j\) 变为 \(rk(i),rk(j)\) 后排序,将 \(A,B\) 变成一个递增的序列,放到 \(sa\) 数组上进行考虑(原来考虑的是后缀 \(i\) 和后缀 \(j\)\(\text{lcp}\),将 \(i,j\) 变为 \(rk(i),rk(j)\) 后变为考虑后缀 \(sa_i,sa_j\)\(\text{lcp}\))。因为 \(sa\) 数组上有性质 \(\text{lcp}(sa_i,sa_j) = \min \limits_{k=i+1}^{j} \text{height}_{k} (i<j)\),所以我们就方便使用数据结构进行整体考虑了。

先假设不存在 \(A_i \neq B_j\)

对于一个 \(A_i\),我们钦定 \(A_i\) 只和小于它的 \(B_j\) 产生贡献。因此,从前往后扫描 \(A_i\),同时用单调栈维护 \(\sum \limits_{B_j \leq x} \text{lcp}(sa_{B_j},sa_{x})\)\(x\) 是我们维护的一个指针,表示我们现在扫到了 \(sa\) 数组的第 \(x\) 位。根据 \(\text{height}\) 数组的性质,当指针 \(x\) 移动到 \(x'\) 时,原有的 \(\text{lcp}(sa_{B_j},sa_{x})\) 变为 \(\text{lcp}(sa_{B_j},sa_{x'})\),要对 \(\text{lcp} (sa_x,sa_{x'})\)\(\min\),对应单调栈的弹出操作。

如果当前的 \(x'\) 指向某个 \(B_j\),则向单调栈添加 \(n - sa_{x'} + 1\) 这个元素(对应该后缀长度)。如果当前的 \(x'\) 指向了 \(A_i\),那么可以直接在单调栈中查询统计好的答案。

对于 \(B_i\),我们也可以钦定 \(B_i\) 只和小于它的 \(A_j\) 产生贡献。据此可以使用另一个单调栈维护贡献。

注意到,如果存在 \(B_i = A_j\),它们的贡献只能被统计一次。于是我们钦定 \(A_i\) 只和小于它的 \(B_j\) 产生贡献,\(B_i\) 只和小于等于它的 \(A_j\) 产生贡献,即可不重不漏地求得答案。

# include <bits/stdc++.h>

const int N=200010,INF=0x3f3f3f3f;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-') f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}

typedef long long ll;

struct Stack{
	int val[N],l[N],top;
	ll sum;
	inline void init(void){
		top=sum=0;
		return;
	}
	inline void check(int cval){
		int len=0;
		while(top&&val[top]>=cval) sum-=1ll*l[top]*val[top],len+=l[top],--top;
		val[++top]=cval,l[top]=len,sum+=1ll*cval*len;
		return;
	}
	inline void push(int cval){
		val[++top]=cval,l[top]=1,sum+=cval;
		return;
	}
}ta,tb;

int a[N],b[N];
int alen,blen;
char s[N];
int n,q;

namespace sa{
	int rk[N],sa[N],ork[N];
	int buc[N],id[N];
	int ht[N];
	int mx=128;
	int mn[N][20],lg[N];
	
	inline void st_init(void){
		for(int i=1;i<=n;++i) mn[i][0]=ht[i],lg[i]=((i==1)?0:lg[i>>1]+1);
		for(int j=1;(1<<j)<=n;++j) for(int i=1;i+(1<<j)-1<=n;++i)
			mn[i][j]=std::min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
		return;
	}
	
	inline int st_query(int l,int r){
		assert(l<=r);
		int t=lg[r-l+1];
		return std::min(mn[l][t],mn[r-(1<<t)+1][t]);
	}
	
	inline int lcp(int l,int r){
		if(l>r) std::swap(l,r);
		if(l==r) return n-sa[l]+1;
		return st_query(l+1,r);
	}
	inline void init(void){
		for(int i=1;i<=n;++i) ++buc[rk[i]=s[i]];
		for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
		for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
		for(int w=1,cc=0;;mx=cc,cc=0,w<<=1){
			for(int i=n-w+1;i<=n;++i) id[++cc]=i;
			for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
			memset(buc,0,sizeof(buc));
			memcpy(ork,rk,sizeof(ork));
			cc=0;
			for(int i=1;i<=n;++i) ++buc[ork[i]];
			for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
			for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];	
			for(int i=1;i<=n;++i)
				rk[sa[i]]=((ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc);
			
			if(cc==n) break;
		}
		for(int i=1,k=0;i<=n;++i){
			if(k) --k;
			while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
			ht[rk[i]]=k;
		}
		st_init();
		return;
	}
	
	inline ll solve(void){
		for(int i=1;i<=alen;++i) a[i]=rk[a[i]];
		for(int i=1;i<=blen;++i) b[i]=rk[b[i]];
		std::sort(a+1,a+1+alen),std::sort(b+1,b+1+blen);
		int i=1,j=1,las=0;
		ll ans=0;
		while(i<=alen||j<=blen){
			int op,pos;
			if(i>alen||(j<=blen&&b[j]<a[i])) op=2,pos=b[j++];
			else op=1,pos=a[i++];
			int lcl=lcp(las,pos);
			ta.check(lcl),tb.check(lcl);			
			if(op==1) ans+=tb.sum,ta.push(n-sa[pos]+1);
			else ans+=ta.sum,tb.push(n-sa[pos]+1);
			las=pos;
		}
		return ans;
	}
}
int main(void){
	n=read(),q=read();	
	scanf("%s",s+1);
	sa::init();
	while(q--){
		alen=read(),blen=read();
		for(int i=1;i<=alen;++i) a[i]=read();
		for(int i=1;i<=blen;++i) b[i]=read();
		printf("%lld\n",sa::solve());
	}
	return 0;
}

练习题(弱化版本)

[ICPC2020 Nanjing R] Baby's First Suffix Array Problem

详见题解 https://www.luogu.com.cn/article/iwhoyvnj。

# include <bits/stdc++.h>

const int N=500010,INF=0x3f3f3f3f;
const int MX=500000;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-') f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}

struct Qdata{
	int id,l,r;
};
std::vector <Qdata> Q[N];
std::vector <int> po[N];

int n,m;
char s[N];

int buc[N],sa[N],rk[N],ht[N],id[N],ork[N],mx;

int ans[N];


inline void init_sa(void){
	
	mx=128;
	std::fill(buc+1,buc+1+mx,0);
	
	rk[n+1]=0;
	
	for(int i=1;i<=n;++i) ++buc[rk[i]=s[i]];
	for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
	for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
	
	for(int w=1,cc=0;w<=n;w<<=1,mx=cc,cc=0){
		for(int i=n-w+1;i<=n;++i) id[++cc]=i;
		for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
		memset(buc,0,(mx+1)*4);
		memcpy(ork,rk,sizeof(ork));
		for(int i=1;i<=n;++i) ++buc[ork[i]];
		for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
		for(int i=n;i;--i)
			sa[buc[ork[id[i]]]--]=id[i];

		cc=0;
		for(int i=1;i<=n;++i)
			rk[sa[i]]=(ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc;
		if(cc==n) break;
	}

	for(int i=1,k=0;i<=n;++i){
		if(k) --k;
		while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
		ht[rk[i]]=k;
	}
	return;
}

int T1[N],T2[N];

inline int lb(int x){
	return x&(-x);
}
inline void add(int *arr,int x,int v){
	for(;x<=MX;x+=lb(x)) arr[x]+=v;
	return;
}
inline int query(int *arr,int x){
	int ans=0;
	for(;x;x-=lb(x)) ans+=arr[x];
	return ans;
}

int c[N];

int v[N*5],vc;

inline void pl(int x){
	v[++vc]=x;
	return;
}
inline void recalc(void){
	std::sort(v+1,v+1+vc),vc=std::unique(v+1,v+1+vc)-(v+1);
	return;
}
inline int d(int x){
	int ans=std::lower_bound(v+1,v+1+vc,x)-v;
	return assert(v[ans]==x),ans;
}

// id op k x
struct squa{
	int id,op,k,x;
};
//id x
struct tri{
	int id,x;
};
std::vector <squa> S[N];
std::vector <tri> T[N];

void solve(int L,int R){
	if(L==R) return;
	int mid=(L+R)>>1;
	solve(L,mid),solve(mid+1,R);
	
	c[mid+1]=ht[mid+1];
	for(int i=mid+2;i<=R;++i) c[i]=std::min(c[i-1],ht[i]);
	
	int e=n;
	vc=0;
	
	for(int i=mid;i>=L;--i){
		for(auto C:Q[i]){
			int l=C.l,r=C.r;
			l=std::max(C.l,sa[i]+1);
			if(l<=r){
				if(l+e<r) l=r-e;
				pl(l),pl(r+1);
			}
			l=std::max(C.l,std::max(sa[i]+1,r-e+1));
			if(l<=r) pl(l),pl(r+1);
		}
		e=std::min(e,ht[i]);
	}
	for(int i=mid+1;i<=R;++i) pl(sa[i]);
	recalc(),e=n;
	
	for(int i=mid;i>=L;--i){
		for(auto C:Q[i]){
			int l=C.l,r=C.r,id=C.id;
			l=std::max(C.l,sa[i]+1);
			if(l<=r){
				if(l+e<r) l=r-e;
				int dr=d(r+1),dl=d(l);
				S[dr].push_back((squa){id,1,-1,e});
				S[dl].push_back((squa){id,1,1,e});
				T[dl].push_back((tri){id,r});
			}
			l=std::max(C.l,std::max(sa[i]+1,r-e+1));
			if(l<=r){
				int dr=d(r+1),dl=d(l);
				S[dr].push_back((squa){id,2,-1,e});
				S[dl].push_back((squa){id,2,1,e});
			}
		}
		e=std::min(e,ht[i]);
	}
	for(int i=mid+1;i<=R;++i){
		if(c[i]) po[d(sa[i])].push_back(c[i]);
	}
	for(int i=vc;i;--i){
		for(auto p:po[i])
			add(T1,p,1),add(T2,p+v[i],1);
		for(auto C:S[i]){
			int ret=query(T1,C.x);
			if(C.op==2) ret=query(T1,n)-ret;
			ans[C.id]+=C.k*ret;
		}
		for(auto C:T[i]){
			int ret=query(T2,C.x);
			ans[C.id]-=ret;
		}
	}
	
	for(int i=1;i<=vc;++i){
		for(auto p:po[i])
			add(T1,p,-1),add(T2,p+v[i],-1);
		po[i].clear(),T[i].clear(),S[i].clear();
	}
	
	c[mid]=n;
	for(int i=mid-1;i>=L;--i) c[i]=std::min(c[i+1],ht[i+1]);
	e=n,vc=0;
	
	for(int j=mid+1;j<=R;++j){
		for(auto C:Q[j]){
			int l=C.l,r=C.r;
			l=std::max(C.l,sa[j]+1);
			if(l<=r) pl(r+1),pl(l);
			l=C.l,r=std::min(C.r,sa[j]-1);
			if(l<=r) pl(r+1),pl(l);
		}
	}
	for(int i=L;i<=mid;++i) pl(sa[i]);
	recalc();
	
	for(int j=mid+1;j<=R;++j){
		e=std::min(e,ht[j]);
		for(auto C:Q[j]){
			int l=C.l,r=C.r,id=C.id;
			l=std::max(C.l,sa[j]+1);
			if(l<=r){
				int dr=d(r+1),dl=d(l);
				S[dr].push_back((squa){id,1,-1,n});
				S[dl].push_back((squa){id,1,1,n});
			}
			l=C.l,r=std::min(C.r,sa[j]-1);
			if(l<=r){
				int dr=d(r+1),dl=d(l);
				int lim=(e<=C.r-sa[j])?n:C.r-sa[j];
				S[dr].push_back((squa){id,1,-1,lim});
				S[dl].push_back((squa){id,1,1,lim});
			}
		}
	}
	for(int i=L;i<=mid;++i){
		po[d(sa[i])].push_back(c[i]);
	}
	for(int i=vc;i;--i){
		for(auto p:po[i]) add(T1,p+1,1);
		for(auto C:S[i]){
			int ret=query(T1,C.x+1);
			ans[C.id]+=C.k*ret;
		}
	}
	for(int i=1;i<=vc;++i){
		for(auto p:po[i])
			add(T1,p+1,-1);
		po[i].clear(),T[i].clear(),S[i].clear();
	}

	return;
}

inline void mian(void){
	n=read(),m=read();
	scanf("%s",s+1);
	init_sa();	
	for(int i=1;i<=m;++i){
		int l=read(),r=read(),k=read();
		Q[rk[k]].push_back((Qdata){i,l,r});
	}
	solve(1,n);
	for(int i=1;i<=m;++i) printf("%d\n",ans[i]+1),ans[i]=0;
	for(int i=1;i<=n;++i) Q[i].clear();	
	return;
}

int main(void){
//	freopen("in.txt","r",stdin);
	int T=read();
	while(T--) mian();
	return 0;
}
posted @ 2024-05-31 08:51  Meatherm  阅读(6)  评论(0编辑  收藏  举报