ShinriiTin/templates/test

CF BETA #30 E.Tricky and Clever Password

检验模板KmpManacherSparseTable

题目大意

给出一个字符串\(S(|S|\le10^5)\),要求将\(S\)分为若干个不相交的部分,满足\(S=A+prefix+B+middle+C+suffix\)

要求\(|prefix|=|suffix|\)\(|middle|\)为奇数,\(prefix+middle+suffix\)为回文串,除了\(middle\)之外任意一部分都可以为空。

现在要最大化\(|prefix|+|middle|+|suffix|\),任意输出一种合法方案。

题解

先枚举\(suffix\)然后找到最左边的可以与它匹配的\(prefix\),可以用原串和反串做一次 Kmp 得到。

之后问题就转化为 \(n\) 次询问,每次询问某个区间中最大的回文串。

一种做法是二分答案,如果区间\([L+M-1,R-M+1]\)中最大的奇回文半径大于等于\(M\),则存在长度为\(2M-1\)的奇回文串。

通过 ST 表预处理可以做到单次询问\(O(\log{n})\)

总的时间复杂度为\(O(n\log{n})\),空间复杂度为\(O(n\log{n})\)

#include <bits/stdc++.h>

const int max_N=1e5;

//indexed from 1
template<size_t size>
struct Kmp{
	int fail[size];
	template<typename T>
	inline void get_fail(T S,int len){
		fail[1]=0;
		for(int i=2,j;i<=len;++i){
			for(j=fail[i-1];j&&S[j+1]!=S[i];j=fail[j]);
			fail[i]= S[j+1]==S[i]?++j:0;
		}
	}
	template<typename T>
	inline void matching(T A,int lenA,T B,int lenB,int*right){
		get_fail(B,lenB);
		for(int i=1,j=0;i<=lenA;++i){
			while(j&&A[i]!=B[j+1])j=fail[j];
			if(A[i]==B[j+1])++j;
			if(!right[j])right[j]=lenA-i+j;
			if(j==lenB)return;
		}

	}
};

//indexed from 1
template<size_t size>
struct Manacher{
	//type:ODD 1,EVEN 0
	//define the center of even string as the left one
	int r[2][size];
	template<typename T>
	inline void build(T S,int len,int type){
		register int i,j,k;
		int*R=r[type];
		for(i=1,j=0;i<=len;i+=k,j=std::max(j-k,0)){
			while(i-j>0&&i+j+type<=len&&S[i-j]==S[i+j+type])++j;
			R[i]=j;
			for(k=1;k<j&&R[i-k]!=R[i]-k;++k)R[i+k]=std::min(R[i-k],R[i]-k);
		}
	}
};

Manacher<max_N+21>manacher;

template<size_t size,size_t size_log>
struct SparseTable{
	int max[size][size_log],bit[size];
	inline int slt(int x,int y){
		return manacher.r[0][x]>manacher.r[0][y]?x:y;
	}
	inline void init(int n){
		for(int i=2;i<=n;++i)bit[i]=bit[i>>1]+1;
		for(int i=1;i<=n;++i)max[i][0]=i;
		for(int j=1;j<=bit[n];++j){
			for(int i=1;i+(1<<j)-1<=n;++i){
				max[i][j]=slt(max[i][j-1],max[i+(1<<(j-1))][j-1]);
			}
		}
	}
	inline int rmq(int l,int r){
		int i=bit[r-l+1];
		return slt(max[l][i],max[r-(1<<i)+1][i]);
	}
};

char str[max_N+21],tmp[max_N+21];

int n,right[max_N+21];

Kmp<max_N+21>kmp;

SparseTable<max_N+21,17>st;

int ans;

std::vector<std::pair<int,int>>vec;

inline int query(int L,int R){
	int l=1,r=R-L+1;
	r=(r&1)?(r+1)/2:r/2;
	int ans=-1;
	while(l<=r){
		int mid=(l+r)>>1;
		int tmp=st.rmq(L+mid-1,R-mid+1);
		if(manacher.r[0][tmp]>=mid){
			ans=tmp;
			l=mid+1;
		}
		else{
			r=mid-1;
		}
	}
	return ans;
}

inline void solve(){
	int x=st.rmq(1,n);
	ans=manacher.r[0][x]*2-1;
	vec.push_back({x-manacher.r[0][x]+1,x+manacher.r[0][x]-1});
	//printf("%d %d\n",x,ans);
	for(int i=1;i<=n;++i){
		if(right[i]<=2*i)continue;	
		int middle=query(i+1,right[i]-i);
		int R=manacher.r[0][middle];
		R=std::min(R,std::min(middle-i,right[i]-i-middle+1));
		int ml=middle-R+1,mr=middle+R-1;
		int len=i*2+(mr-ml+1);
		if(len>ans){
			ans=len;
			vec.clear();	
			vec.push_back({right[i]-i+1,right[i]});
			vec.push_back({ml,mr});
			vec.push_back({1,i});
		}
	}
}

int main(){
	scanf("%s",str+1);
	n=strlen(str+1);
	strcpy(tmp+1,str+1);
	std::reverse(str+1,str+1+n);
	kmp.matching(tmp,n,str,n,right);
	for(int i=1;i<=n;++i)if(!right[i])right[i]=i;
	manacher.build(str,n,0);
	manacher.build(str,n,1);
	st.init(n);
	solve();
	printf("%d\n",(int)vec.size());
	for(auto&x:vec)printf("%d %d\n",n-x.second+1,x.second-x.first+1);
	return 0;
}

Problem K. A Text Problem

检验模板SuffixArray

题目大意

定义字符串\(A\)在字符串\(B\)中的位置\(i\)满足允许错一次的匹配成功当且仅当存在将\(A\)中某一个位置的字符替换后得到的字符串\(A^{'}\)(可能替换后\(A=A^{'}\))满足\(\forall j\in [0,|A^{'}|),S[i+j]=A^{'}[j]\)

给出字符串\(S\),和若干次询问,每次询问给出一个字符串\(T\),满足\(|S|\ge|T|\),问\(T\)能在\(S\)中的多少个位置满足允许错一次的匹配成功。

保证\(|S|\le2\times10^5,\sum|T|\le2\times10^5\)

题解

\(S\)的长度为\(n\)\(T\)的长度为\(m\)

先求出\(S\)的后缀数组\(A\),及其名次数组\(posA\),然后对于\(T\)的每个后缀\(T[i\cdots (m-1)]\)

  • 找到最小的\(k\)满足\(\displaystyle S[A[k]\cdots(n-1)] \ge T[ i...(m-1)]\),记为\(a1[i]\)
  • 再找到最大的\(k\)满足\(\displaystyle S[A[k]\cdots\min(n-1,A[k]+m-i-1)] \le T[i\cdots(m-1)]\),记为\(a2[i]\)

显然有\(\displaystyle S[j\cdots (j+m-i-1)]=T[i\cdots(m-1)]\)当且仅当\(\displaystyle a1[i]\le posA[j] \le a2[i]\)

同理,我们求出\(S\)的反串\(S1\)的后缀数组\(A1\),记\(B[i]=n-A1[i]\),令\(posB[B[i]]=i\)

\(\displaystyle reverse(S[0\cdots(B[i]-1)])\)是字典序第\(i\)小的反前缀。

然后对于\(T\)的每个反前缀\(\displaystyle reverse(T[0\cdots i])\)

  • 找到最小的\(k\)满足\(\displaystyle reverse(S[0\cdots(B[k]-1)]) \ge reverse(T[0\cdots i])\),记为\(b1[i]\)
  • 找到最大的\(k\)满足\(\displaystyle reverse(S[\max(0,B[k]-1-i)\cdots(B[k]-1)]) \le reverse(T[0\cdots i])\),记为\(b2[i]\)

显然有\(\displaystyle S[(j-i)\cdots j] = T[0\cdots i]\)当且仅当\(\displaystyle b1[i]\le posB[j+1] \le b2[i]\)

因此,枚举\(T\)\(S\)在匹配时允许出错的位置分别为\(i\)\(j\),则匹配成功的充要条件为 \[ \displaystyle S[(j-i)\cdots(j-1)]=T[0\cdots(i-1)] \land S[(j+1)\cdots(j-i+m-1)]=T[(i+1)\cdots(m-1)] \] 即: \[ \displaystyle a1[i+1]\le posA[i+1] \le a2[i+1] \land b1[i-1]\le posB[i] \le b2[i-1] \] 这样就成功将原问题转化为了二维数点问题。

但是这样做还有一点小bug:如果\(T\)可以不允许失败地在\(S\)中匹配成功,则会重复计算这一部分的答案。

解决方法很简单,肯定是每次 严格成功的匹配被计算了\(m\)次,答案减去\((b2[m-1]-b1[m-1]+1)\times(m-1)\)即可。

因此最终的答案为 \[ \displaystyle ans = \sum\limits_{i=0}^{m-1} count(a1[i+1],a2[i+1],b1[i-1],b2[i-1]) - [b1[m-1]\le b2[m-1]]\times(b2[m-1]-b1[m-1]+1)\times(m-1) \] 二维数点的部分可以预处理主席树在线回答询问,时间复杂度\(O(n\log{n}+\sum |T|\log{n})\)

那么现在问题只剩下如何去求\(a1,a2,b1,b2\)了。

我们可以倒着求\(a1\)\(a2\),显然有\(a1[m]=0,a2[m]=n\)

假如我们已知了\(a1[i+1]\cdots a1[m]\)\(a2[i+1]\cdots a2[m]\),那么:

  • \(\displaystyle S[A[k]\cdots(n-1)] < T[ i...(m-1)]\)等价于\(\displaystyle A[k]=n \lor S[A[k]]<T[i] \lor (S[A[k]]=T[i]\land posA[A[k]+1]<a1[i+1])\)
  • \(\displaystyle S[A[k]\cdots(n-1)] > T[ i...(m-1)]\)等价于\(\displaystyle A[k]<n\land (S[A[k]]>T[i] \lor (S[A[k]]=T[i]\land posA[A[k]+1]>a2[i+1]))\)

因此,我们可以二分求解,时间复杂度为\(O(n\log{n})\)

同理我们可以顺序求解\(b1\)\(b2\),显然有\(b1[-1]=0,b2[-1]=n\)

假如我们已知了\(b1[-1]\cdots b1[i-1]\)\(b2[-1]\cdots b2[i-1]\),那么:

  • \(\displaystyle reverse(S[0\cdots(B[k]-1)]) < reverse(T[0\cdots i])\)等价于$k=0 S[B[k]-1]<T[i] (S[B[k]-1]=T[i]posB[B[k]-1]<b1[i-1]) $
  • \(\displaystyle reverse(S[0\cdots(B[k]-1)]) > reverse(T[0\cdots i])\)等价于\(\displaystyle k>0 \land (S[B[k]-1]>T[i] \lor (S[B[k]-1]=T[i]\land posB[B[k]-1]>b1[i-1]) )\)

同样,我们可以二分求解,时间复杂度为\(O(n\log{n})\)

至此,我们就解决了这个问题,整个算法时间复杂度为\(O((\sum|S|+\sum|T|)\log{|S|})\)

#include <bits/stdc++.h>

#define all_of(x) (x).begin(),(x).end()

using ll = long long;

const int max_N=int(2e5)+21;

namespace SuffixArray{
	int sa[max_N];
	int tmp[max_N],cnt[max_N],bc[max_N];
	inline void build(char*s,int n){
		register int i,T,sigma;
		
		for(i=0;i<n;++i)sa[i]=i;
		std::sort(sa,sa+n,[&](int a,int b){return s[a]<s[b];});
		for(i=1,bc[sa[0]]=1;i<n;++i)bc[sa[i]]=bc[sa[i-1]]+(s[sa[i-1]]<s[sa[i]]);
		
		for(T=1;(sigma=bc[sa[n-1]])<n;T<<=1){
			auto cmp=[&s,&T,&n](const int&a,const int&b){
				if(bc[a]!=bc[b])return bc[a]<bc[b];
				return (a+T>=n||b+T>=n)?a>b:bc[a+T]<bc[b+T];
			};
			
			for(i=0;i<=sigma;++i)cnt[i]=0;
			for(i=0;i<n;++i)++cnt[(i+T>=n)?0:bc[i+T]];
			for(i=1;i<=sigma;++i)cnt[i]+=cnt[i-1];
			for(i=n-1;~i;--i)tmp[--cnt[(sa[i]+T>=n)?0:bc[sa[i]+T]]]=sa[i];
			
			for(i=0;i<=sigma;++i)cnt[i]=0;
			for(i=0;i<n;++i)++cnt[bc[i]];
			for(i=1;i<=sigma;++i)cnt[i]+=cnt[i-1];
			for(i=n-1;~i;--i)sa[--cnt[bc[tmp[i]]]]=tmp[i];
			
			for(i=1,tmp[sa[0]]=1;i<n;++i)tmp[sa[i]]=tmp[sa[i-1]]+cmp(sa[i-1],sa[i]);
			memcpy(bc,tmp,sizeof(int)*n);
		}
	}
};

int n;

int A[max_N],B[max_N],posA[max_N],posB[max_N];

int a1[max_N],a2[max_N],b1[max_N],b2[max_N];

char s[max_N],q[max_N];

using star=struct node*;

struct node{
	int sum; star l,r;
}pool[max_N*20];

star rt[max_N],tail,null;

void build(star&x,star y,int l,int r,int t){
	x=tail++,*x=*y,++x->sum;
	if(l==r)return; int mid=(l+r)>>1;
	if(t>mid)build(x->r,y->r,mid+1,r,t);
	else build(x->l,y->l,l,mid,t);
}

inline ll count(int a,int b,int c,int d){
	// count number of points in [a,b] x [c,d]
	std::function<int(star,int,int)>
	calc=[&calc,&c,&d](star x,int l,int r){
		if(x==null||(c<=l&&r<=d))return x->sum;
		int mid=(l+r)>>1,res=0;
		if(c<=mid)res+=calc(x->l,l,mid);
		if(d>mid) res+=calc(x->r,mid+1,r);
		return res;
	};
	int res=calc(rt[b],0,n);
	if(a)res-=calc(rt[a-1],0,n);
	return res;
}

inline void init(){
	register int i;
	
	scanf("%s",s);
	n=strlen(s);
	
	// A[i] is the ith smallest suffix: s[A[i]...n-1]
	SuffixArray::build(s,n);
	for(A[i=0]=n;i<n;++i)A[i+1]=SuffixArray::sa[i]; 
	std::reverse(s,s+n);
	
	// B[i] is the ith smallest rev_prefix: rev(s[0...B[i]-1])
	SuffixArray::build(s,n);
	for(B[i=0]=0;i<n;++i)B[i+1]=n-SuffixArray::sa[i];
	std::reverse(s,s+n);
	
	for(i=0;i<=n;++i)posA[A[i]]=posB[B[i]]=i;
	
	// maintain 2d-points (posA[i+1],posB[i])
	tail=pool;
	null=tail++,null->l=null->r=null,null->sum=0;
	star last=null;
	for(int i=0;i<=n;++i){
		if(A[i])build(last,last,0,n,posB[A[i]-1]);
		rt[i]=last;
	}
}

inline void solve(){
	int Q,m,lo,hi,mi; 
	
	scanf("%d",&Q);
	
	while(Q--){
		scanf("%s",q);
		m=strlen(q);
		
		a1[m]=0,a2[m]=n;
		for(int i=m-1;~i;--i){
			// binary-searching 1:
			// a1[i] is the smallest k
			// s.t. s[A[k]...n-1] >= q[i...m-1]
			lo=0,hi=n+1;
			while(lo<hi){
				auto check=[&i](const int&mi){
					int tmp=A[mi]; if(tmp==n)return true;
					return s[tmp]<q[i]||(s[tmp]==q[i]&&posA[tmp+1]<a1[i+1]);
				};
				if(check(mi=(lo+hi)>>1))lo=mi+1; else hi=mi;
			}
			a1[i]=lo;
			// binary-searching 2:
			// a2[i] is the largest k
			// s.t. s[A[k]...min(n,A[k]+(m-i))-1] <= q[i...m-1]
			lo=0,hi=n;
			while(lo<hi){
				auto check=[&i](const int&mi){
					int tmp=A[mi]; if(tmp==n)return false;
					return s[tmp]>q[i]||(s[tmp]==q[i]&&posA[tmp+1]>a2[i+1]);
				};
				if(check(mi=(lo+hi+1)>>1))hi=mi-1; else lo=mi;
			}
			a2[i]=lo;
		}
		
		ll ans=0; int b1=0,b2=n;
		for(int i=0;i<m&&b1<=b2;++i){
			if(a1[i+1]<=a2[i+1])ans+=count(a1[i+1],a2[i+1],b1,b2);
			// binary-searching 3:
			// b1 is the smallest k
			// s.t. rev(s[0...B[k]-1]) >= rev(q[0...i])
			lo=0,hi=n+1;
			while(lo<hi){
				auto check=[&i,&b1](const int&mi){
					int tmp=B[mi]; if(!tmp)return true;	
					return s[tmp-1]<q[i]||(s[tmp-1]==q[i]&&posB[tmp-1]<b1);
				};
				if(check(mi=(lo+hi)>>1))lo=mi+1; else hi=mi;
			}
			b1=lo;
			// binary-searching 4:
			// b2 is the largest k
			// s.t. rev(s[max(0,B[k]-1-i)...B[k]-1]) <= rev(q[0...i]);
			lo=0,hi=n;
			while(lo<hi){
				auto check=[&i,&b2](const int&mi){
					int tmp=B[mi]; if(!tmp)return false;
					return s[tmp-1]>q[i]||(s[tmp-1]==q[i]&&posB[tmp-1]>b2);
				};
				if(check(mi=(lo+hi+1)>>1))hi=mi-1; else lo=mi;
			}
			b2=lo;
		}
		
		if(b1<=b2)ans-=1ll*(b2-b1+1)*(m-1);
		
		// ans = sigma i in [0,m) count(a1[i+1],a2[i+1],b1[i-1],b2[i-1]) 
		//       - [b1[m-1] <= b2[m-1]] * (b2[m-1]-b1[m-1]+1) * (m-1)
		
		printf("%lld\n",ans);
	}
}

int main(){
	int T; scanf("%d",&T);
	while(T--)init(),solve();
	return 0;
}