20200706 字符串專題

CF653F Paper task

求給出的括號序列本質不同的合法括號子串個數,n5105n\le5*10^5

對每種前綴和分開統計的做法很好想。二分,區間最小值滿足條件(單調棧),去重就後綴數組調整二分範圍。詳見 blog

後綴自動機也差不多,對每個節點找任意一個endpos,同樣限制出一個範圍。詳見 blog2

Code

CF587F Duff is Mad

在這裏插入圖片描述
統計多串在某串中的出現次數,容易想到AC自動機。

sl...rs_{l...r}sks_k 中的出現次數相當於是在 failfail 樹上對 sl...rs_{l...r} 對應結束節點的子樹權值 +1 後 sks_k 的所有節點的權值和。

差分之後,相當於求一個前綴與 sks_k 的答案。
對於 skTs_k\le T,可以暴力遍歷 sks_k 的所有節點,離線掃描 sl...rs_{l...r} 子樹加,單點查。
對於 sk>Ts_k> T,總的串個數不超過 mT\frac {m}T,可以 O(m)O(m) 處理所有與之有關的詢問:預處理 failfail 樹上每個點的子樹中有多少 sks_k 的節點,然後離線掃描 sl...rs_{l...r} 加入和。

詳細的複雜度分析見 xht37’s blog

Code

CF914F Substrings in a String

在這裏插入圖片描述
y105\sum |y|\le10^5

分塊,大小爲 SS,每個塊建後綴自動機,有修改就重構對應塊。O(nS)O(nS)
查詢 y,[l,r]y,[l,r],整塊塊內就在自動機上走,答案爲endpos的個數。跨越塊或者散塊暴力KMP匹配。O(y(nS+S))O(|y|*(\frac nS+S))

可以 bitset 暴力匹配,存每個位置匹配到第 ii 位是否仍然合法,然後繼續加入 i+1i+1 位。最後差分一下。

Code:

#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
int n,m;
char s[maxn],t[maxn];
bitset<maxn>a[26],ans;
int main()
{
	scanf("%s",s+1),n=strlen(s+1);
	for(int i=1;i<=n;i++) a[s[i]-='a'][i]=1;
	scanf("%d",&m);
	for(int op,x,y,c;m--;){
		scanf("%d%d",&op,&x);
		if(op==1){
			getchar(),c=getchar()-'a';
			a[s[x]][x]=0,a[s[x]=c][x]=1;
		}
		else{
			scanf("%d%s",&y,t); int len=strlen(t);
			ans.set();
			for(int i=0;i<len;i++) ans&=a[t[i]-'a']>>i;
			printf("%d\n",(y-x+1>=len)?(ans>>x).count()-(ans>>(y-len+2)).count():0);
		}
	}
}

CF741E Arpa’s abnormal DNA and Mehrdad’s deep interest

在這裏插入圖片描述
洛谷題解區的 Durant_Lee 的題解寫的很清晰了。

比較兩個位置的字典序分成五部分(比較三段),後綴數組LCP比較。(略麻煩)
然後就可以 sort 給所有位置排序求出排名,轉化爲求詢問區間裏的排名最小值。

對於 k>Sk>S,可以枚舉每一塊,然後求在區間 [l,r][l,r][tk+x,tk+y][tk+x,tk+y] 的最小值,一個全局RMQ即可。O(nlogn+qnS)O(n\log n+q*\frac nS)
對於 kSk\le S,離線詢問,對每種 kk 分開做,然後枚舉餘數 t[0,k)t\in[0,k),對所有 it(modk)i \equiv t\pmod k 建立RMQ, 對於 t[x,y]t\in[x,y] 的詢問求 [l,r][l,r] 的最小值即可。O(nlognS+qS)O(n\log n*S+qS)

Code:

#include<bits/stdc++.h>
#define maxn 200005
using namespace std;
const int S = 150;
int n,m,lg[maxn];
namespace SA{
	int ary[4][maxn],b[maxn],st[18][maxn];
	int *sa=ary[0],*rk=ary[1],*nsa=ary[2],*nrk=ary[3];
	void build(int n,char *a){
		for(int i=1;i<=n;i++) b[a[i]]++;
		for(int i=1;i<=255;i++) b[i]+=b[i-1];
		for(int i=1;i<=n;i++) sa[b[a[i]]--]=i;
		for(int i=1;i<=n;i++) rk[sa[i]]=rk[sa[i-1]]+(a[sa[i-1]]!=a[sa[i]]);
		for(int k=1;rk[sa[n]]<n;k<<=1){
			for(int i=1;i<=n;i++) b[rk[sa[i]]]=i;
			for(int i=n;i>=1;i--) if(sa[i]-k>0) nsa[b[rk[sa[i]-k]]--]=sa[i]-k;
			for(int i=n-k+1;i<=n;i++) nsa[b[rk[i]]--]=i;
			for(int i=1;i<=n;i++) nrk[nsa[i]]=nrk[nsa[i-1]]+(rk[nsa[i-1]]!=rk[nsa[i]]||rk[nsa[i-1]+k]!=rk[nsa[i]+k]);
			swap(sa,nsa),swap(rk,nrk);
		}
		for(int i=1,j,k=0;i<=n;st[0][rk[i]]=k,i++)
			for(k&&(k--),j=sa[rk[i]-1];a[i+k]==a[j+k];k++);
		for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
		for(int j=1;j<=lg[n];j++)
			for(int i=1,l=1<<j;i+l-1<=n;i++)
				st[j][i]=min(st[j-1][i],st[j-1][i+(l>>1)]);
	}
	int LCP(int x,int y){
		if(x==y) return maxn;
		if((x=rk[x])>(y=rk[y])) swap(x,y); x++;
		int k=lg[y-x+1];
		return min(st[k][x],st[k][y-(1<<k)+1]);
	}
	int cmp(int x,int y,int l){return LCP(x,y)>=l?0:rk[x]<rk[y]?-1:1;}
	bool cmp2(int x,int y){//equal return 1.
		int f=x>y,t; if(f) swap(x,y);
		if(x+m<=y){
			if(t=cmp(n+1,x+1,m)) return (t<0)^f;
			if(t=cmp(x+1,x+m+1,y-(x+m))) return (t<0)^f;
			if(t=cmp(y+1-m,n+1,m)) return (t<0)^f;
		}
		else{
			if(t=cmp(n+1,x+1,y-x)) return (t<0)^f;
			if(t=cmp(n+1+y-x,n+1,x+m-y)) return (t<0)^f;
			if(t=cmp(x+1,n+1+x+m-y,y-x)) return (t<0)^f;
		}
		return 1^f;
	}
}
int Q,RK[maxn],PS[maxn],ans[maxn],mn[18][maxn];
char s1[maxn],s2[maxn];
struct node{
	int l,r,k,x,y,id;
	void init(int i){id=i,scanf("%d%d%d%d%d",&l,&r,&k,&x,&y);}
	bool operator < (const node &p)const{return k<p.k;}
}q[maxn];
int RMQ(int x,int y){
	if(x>y) return maxn;
	int k=lg[y-x+1];
	return min(mn[k][x],mn[k][y-(1<<k)+1]);
}
void Pre(int N){
	for(int j=1;j<=lg[N+1];j++) for(int i=0,l=1<<j;i+l-1<=N;i++) mn[j][i]=min(mn[j-1][i],mn[j-1][i+(l>>1)]);
}
int main()
{
	scanf("%s%s%d",s1+1,s2+1,&Q),n=strlen(s1+1),m=strlen(s2+1);
	for(int i=1;i<=m;i++) s1[n+i]=s2[i];
	SA::build(n+m,s1);
	for(int i=0;i<=n;i++) PS[i]=i;
	sort(PS,PS+n+1,SA::cmp2);
	for(int i=0;i<=n;i++) RK[PS[i]]=i;
	for(int i=1;i<=Q;i++) q[i].init(i),ans[i]=maxn;
	
	for(int i=0;i<=n;i++) mn[0][i]=RK[i]; Pre(n);
	for(int i=1;i<=Q;i++) if(q[i].k>S)
		for(int j=0,k=q[i].k;j*k<=n;j++)
			ans[i]=min(ans[i],RMQ(max(j*k+q[i].x,q[i].l),min(j*k+q[i].y,q[i].r)));
	
	sort(q+1,q+1+Q);
	for(int k=1,s=1,t;k<=S&&k<=n&&s<=Q;k++) if(q[s].k==k){
		for(t=s;t<Q&&q[t+1].k==k;t++);
		for(int r=0;r<k;r++){
			int M = n/k-(n%k<r);
			for(int i=0;i<=M;i++) mn[0][i]=RK[i*k+r]; Pre(M);
			for(int i=s;i<=t;i++) if(q[i].x<=r&&r<=q[i].y)
				ans[q[i].id]=min(ans[q[i].id],RMQ(q[i].l/k+(q[i].l%k>r),q[i].r/k-(q[i].r%k<r)));
		}
		s=t+1;
	}
	
	for(int i=1;i<=Q;i++) printf("%d%c",ans[i]<maxn?PS[ans[i]]:-1,i==Q?10:32);
}

CF862F Mahmoud and Ehab and the final stage

在這裏插入圖片描述

題解

區間LCP是相鄰兩兩LCP的最小值。

對 LCP <= K 的每種LCP開一棵線段樹,如果某個位置>=LCP就爲1,否則爲0,查詢答案就是 (區間最大子段和+1) * LCP。O((n+q)Klogn)O((n+q)K\log n)
對 LCP > K,串的個數不超過 總長/K,把這些串拿出來建笛卡爾樹,dfs求答案。可以用 vector 存這些位置,修改的時候暴力插入。O(qLK)O(q*\frac LK)

KKnlogn\sqrt {\frac n{\log n}} 時複雜度 O(nnlogn)O(n\sqrt {n\log n}),實際 KK 取大一點。

[CTSC2010]珠寶商

在這裏插入圖片描述
暴力的做法是從一個樹上節點開始走,在S的後綴自動機中匹配,統計endpos大小的和。

涉及路徑問題,通常的想法是點分治。
統計經過點分中心 xx 的路徑有多少在 SS 中。
在這裏插入圖片描述
暴力算法 BB 中,T[y...x]T[y...x]S[1...i]S[1...i] 的條件是 T[y...x]T[y...x]SS 的後綴自動機中能夠匹配,且 endpos 集合包含 ii。注意這裏是在 TT 的前面添加一個點,在後綴自動機上不是走轉移邊,而是走 parentparent 樹的轉移邊:(下面這張圖截自洛谷題解區 WAPER420 的題解
在這裏插入圖片描述

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章