字符串作業(上)

「雅禮集訓 2017 Day7」事情的相似度

求區間[l,r][l,r]內兩個前綴s[1..a],s[1...b](a,b[l,r],ab)s[1..a],s[1...b](a,b\in [l,r],a\neq b)的最長公共後綴長度的最大值。

後綴自動機,用LCT\texttt{LCT}維護fail\texttt{fail}樹。
離線,從前到後每次加入一個前綴,將這個前綴在後綴自動機上的點accessaccess
發現這是一個每次將樹上一個點到根的路徑染色的形式。
accessaccess時維護每個點被最後一次染色的時間。
當新的一次accessaccess抵達一個點xx時,這個xx的最後一次染色和這次染色可以看做這兩個前綴的lcalca到根的路徑包含xx
直接對於最後一次染色的時間在樹狀數組上對xx的字符串長度取max\max
處理到前綴rr時在樹狀數組上回答所有[l,r][l,r]的詢問即可。

AC Code\mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 200005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,m;
namespace BIT{
	int tr[maxn];
	void upd(int u,int v){ for(;u;u-=u&-u) tr[u]=max(tr[u],v); }
	int qry(int u){ int r=0;for(;u<=n;u+=u&-u) r=max(r,tr[u]);return r; }
}
char s[maxn];
int fa[maxn],ch[maxn][2],tr[maxn][2],tg[maxn],pos[maxn],len[maxn],tot=1,last=1;
void ins(int c){ // rooted 1 
	int u = ++tot , p = last , q;
	len[last = u] = len[p] + 1;
	for(;p && !tr[p][c];p=fa[p]) tr[p][c]=u;
	if(!p) fa[u] = 1;
	else if(len[q = tr[p][c]] == len[p] + 1) fa[u] = q;
	else{
		int v = ++tot;
		tr[v][0] = tr[q][0],
		tr[v][1] = tr[q][1],
		len[v] = len[p] + 1,
		fa[v] = fa[q];
		for(;p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
		fa[u] = fa[q] = v;
	}
}
#define pa fa[x]
int inr(int x){ return ch[pa][1] == x; }
int isr(int x){ return ch[pa][0] ^ x && ch[pa][1] ^ x; }
void dt(int x){ 
	if(ch[x][0]) tg[ch[x][0]] = tg[x];
	if(ch[x][1]) tg[ch[x][1]] = tg[x];
}
void dtpath(int x){ if(!isr(x)) dtpath(pa);dt(x); }
void rot(int x){ 
	int y = fa[x] , z = fa[y] , c = inr(x);
	if(!isr(y)) ch[z][inr(y)] = x;
	(ch[y][c] = ch[x][!c]) && (fa[ch[y][c]] = y);
	fa[fa[ch[x][!c] = y] = x] = z;
}
void splay(int x){
	for(dtpath(x);!isr(x);rot(x))
		if(!isr(pa))
			rot(inr(pa) ^ inr(x) ? x : pa);
}
int access(int x,int id){
	int y = 0;
	for(;x;x=fa[y=x]){
		splay(x);
		if(tg[x])
			BIT::upd(tg[x] , len[x]);
		ch[x][1] = y;
		tg[x] = id;
	}
	return y;
}
vector<int>G[maxn];
int l[maxn],r[maxn],ans[maxn];

int main(){
	scanf("%d%d",&n,&m);
	scanf("%s",s+1);
	rep(i,1,n) ins(s[i]-'0'),pos[i] = last;
	rep(i,1,m) scanf("%d%d",&l[i],&r[i]),G[r[i]].push_back(i);
	rep(i,1,n){
		access(pos[i],i);
		rep(j,0,G[i].size()-1){
			int v = G[i][j];
			ans[v] = BIT::qry(l[v]);
		}
	}
	rep(i,1,m) printf("%d\n",ans[i]);
}

「十二省聯考 2019」字符串問題

給定一個字符串,給出nan_a個點,每個點是字符串中的一個子串S[l...r]S[l...r],每個點有若干類邊,每類邊可以被表示爲該點連向 包含字符串某一個子串S[a...b]S[a...b]作爲前綴 的點,求最長路或判斷沒有最長路。

發現作爲前綴這個條件,可以簡單的對反串建後綴自動機,則連上fail\rm fail樹中父親到兒子的有向邊即可表示作爲前綴這個條件。
但是有一個細節上的問題就是後綴自動機的點是多個不同的串,在後綴自動機上是同一個點的串有時需要連邊有時又不需要連邊,這個問題很好解決,就對於每個點存一下所有串,按長度排個序再連邊即可。
注意後綴自動機上串定位可以用倍增實現。

AC Code\mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 800005
#define maxc 26
#define LL long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define lim 19
using namespace std;

int n;
char S[maxn];
int fa[maxn],f[lim][maxn],tr[maxn][maxc],len[maxn],pos[maxn],tot,last;
void ins(int c){
	int u = ++tot , p = last , q;
	len[last = u] = len[p] + 1;
	for(;~p && tr[p][c]==0;p=fa[p]) tr[p][c] = u;
	if(p==-1) fa[u] = 0;
	else if(len[q=tr[p][c]] == len[p] + 1) fa[u] = q;
	else{
		int v = ++tot;
		memcpy(tr[v],tr[q],sizeof tr[q]);
		fa[v] = fa[q] , len[v] = len[p] + 1;
		for(;~p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
		fa[q] = fa[u] = v;
	}
}

vector<int>G[maxn];int Le[maxn],na,nb;
LL dis[maxn];
int info[maxn],Prev[maxn<<2],to[maxn<<2],cst[maxn<<2],IN[maxn],cnt_e,cnt_p;
void Node(int u,int v,int c=0){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=c,IN[v]++; }
int in[maxn],ot[maxn];
bool cmp(const int &u,const int &v){ return Le[u] == Le[v] ? u > v : Le[u] < Le[v]; }

int main(){
	
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	
	int T;
	fa[0] = -1;
	for(scanf("%d",&T);T--;){
		scanf("%s",S+1);
		n = strlen(S+1);
		reverse(S+1,S+n+1);
		rep(i,1,n) ins(S[i]-'a'),pos[i]=last;
		rep(i,1,tot) f[0][i] = fa[i];
		rep(j,1,lim-1) rep(i,1,tot) f[j][i] = f[j-1][f[j-1][i]];
		scanf("%d",&na);
		rep(i,1,na){
			int l,r;
			scanf("%d%d",&l,&r);
			swap(l,r);
			l = n - l + 1 , r = n - r + 1;
			Le[i] = r - l + 1;
			int u = pos[r];
			per(j,lim-1,0) if(len[f[j][u]] >= Le[i])
				u = f[j][u];
			G[u].push_back(i);
		}
		scanf("%d",&nb);
		rep(i,1,nb){
			int l,r;
			scanf("%d%d",&l,&r);
			swap(l,r);
			l = n - l + 1 , r = n - r + 1;
			Le[i+na] = r - l + 1;
			int u = pos[r];
			per(j,lim-1,0) if(len[f[j][u]] >= Le[i+na])
				u = f[j][u];
			G[u].push_back(i+na);
		}
		cnt_p = na + nb;
		rep(i,1,tot){
			sort(G[i].begin(),G[i].end(),cmp);
			in[i] = ++cnt_p;
			int p = in[i];
			rep(j,0,G[i].size()-1){
				if(G[i][j] <= na) 
					Node(p,G[i][j],Le[G[i][j]]);
				else 
					Node(p,G[i][j]),p=G[i][j];
			}
			ot[i] = p;
		}
		++cnt_p;
		rep(i,1,tot) if(fa[i]) Node(ot[fa[i]],in[i]);
		rep(i,1,na) Node(cnt_p,i,Le[i]);
		int m;scanf("%d",&m);
		rep(i,1,m){
			int x,y;scanf("%d%d",&x,&y);
			Node(x,y+na);
		}
		
		queue<int>q;
		rep(i,1,cnt_p) if(!IN[i]) q.push(i),dis[i]=0;
			else dis[i] = -0x3f3f3f3f3f3f3f3fll;
		LL ans = 0;
		for(int u;!q.empty();){
			u=q.front(),q.pop();
			ans = max(ans , dis[u]);
			for(int j=info[u],v;j;j=Prev[j]){
				v = to[j];
				dis[v] = max(dis[v] , dis[u] + cst[j]);
				if((--IN[v]) == 0) q.push(v);
			}
		}
		rep(i,1,cnt_p) if(IN[i]) ans = -1;
		printf("%lld\n",ans);
		
		memset(tr,0,sizeof(tr[0])*(tot+1));
		rep(i,1,tot) fa[i] = 0 , G[i].clear();
		rep(i,1,cnt_p) info[i] = IN[i] = 0;
		last = tot = cnt_e = 0;
	}
}

「雅禮集訓 2017 Day1」字符串

給出一個字符串ss,長度爲nnmm[li,ri][l_i,r_i],一個固定的kk
qq次詢問每次給出一個長度爲kk的字符串ww,求w[li...ri](i[1,m])w[l_i...r_i](i\in [1,m])ss中出現次數的總和。

發現正常的做法,mm是瓶頸,考慮設計一個和mm無關的算法。
設定閾值SS,當kSk \leq S時,我們暴力算出所有w[a...b](1abk)w[a...b](1 \leq a \leq b \leq k)ss中的出現次數並暴力統計,複雜度O(nk×k2)=O(nk)O(\frac nk \times k^2) = O(nk)(這裏其實應該還需要兩次lower_boundlower\_bound的,但是因爲是mm個詢問平攤到k2k^2個位置中(顯然平攤時操作次數最多),所以是logmk2\log\frac m{k^2},實際上是在k2k^2靠近O(m)O(m)時完全可以忽略(這也是下文複雜度分析的基礎),在kk更小時就算是O(nklogn)O(nk \log n)也不足爲慮。)
kSk \geq S時,我們在後綴自動機上定位w[li...ri]w[l_i...r_i]計算在ss中出現次數,複雜度爲O(nk×mlogn)=O(nmlognk)O(\frac nk \times m\log n) = O(\frac {nm\log n}k)
平衡一下複雜度,O(nS)=O(nmlognS)O(nS) = O(\frac {nm\log n}S)
S=mlognS = \sqrt {m\log n}時最優爲:O(nmlogn)O(n\sqrt {m\log n})
(實際表現十分好。)

#include<bits/stdc++.h>
#define maxn 200005
#define S 1300
#define maxc 26
#define lim 18
#define LL long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,m,q,K;
char s[maxn],w[maxn];
int fa[maxn],tr[maxn][maxc],len[maxn],sm[maxn],tot,last,pos[maxn],l[maxn],r[maxn],c[maxn],f[lim][maxn];
vector<int>G[S+5][S+5];

void ins(int c){
	int u = ++tot , p =last , q;
	len[last = u] = len[p] + 1;
	for(;~p && !tr[p][c];p=fa[p]) tr[p][c] = u;
	if(p == -1) fa[u] = 0;
	else if(len[q=tr[p][c]]==len[p]+1) fa[u] = q;
	else{
		int v = ++tot;
		memcpy(tr[v],tr[q],sizeof tr[q]);
		fa[v] = fa[q] , len[v] = len[p] + 1;
		for(;~p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
		fa[q] = fa[u] = v;
	}
	sm[u]++;
}

void radix_sort(int *a,int *sa,int n,int m){
	static int c[maxn]={};
	rep(i,0,m) c[i] = 0;
	rep(i,0,n) c[a[i]]++;
	rep(i,1,m) c[i] += c[i-1];
	per(i,n,0) sa[--c[a[i]]] = i;
}

int main(){
	scanf("%d%d%d%d",&n,&m,&q,&K);
	fa[0] = -1;
	scanf("%s",s);
	rep(i,0,n-1) ins(s[i] - 'a') , pos[i] = last;
	rep(i,0,tot) c[i] = i;
	radix_sort(len,c,tot,n);
	per(i,tot,1) sm[fa[c[i]]] += sm[c[i]];
	rep(i,0,m-1) scanf("%d%d",&l[i],&r[i]);
	if(K <= S){
		rep(i,0,m-1) G[l[i]][r[i]].push_back(i);
		for(int a,b;q--;){
			scanf("%s",w);
			scanf("%d%d",&a,&b);
			LL ans = 0;
			rep(i,0,K-1){
				int u = 0 , nl = 0;
				rep(j,i,K-1){
					int v = w[j] - 'a';
					for(;~u && !tr[u][v];u = fa[u]);
					if(u == -1) nl = 0 , u = 0;
					else nl = min(nl + 1 , len[u] + 1) , u = tr[u][v];
					if(nl < j-i+1) break;
					ans += 1ll * sm[u] * (upper_bound(G[i][j].begin(),G[i][j].end(),b) - lower_bound(G[i][j].begin(),G[i][j].end(),a));
				}
			}
			printf("%lld\n",ans);
		}
	}
	else{
		rep(i,1,tot) f[0][i] = fa[i];
		rep(j,1,lim-1) rep(i,1,tot) f[j][i] = f[j-1][f[j-1][i]];
		for(int a,b;q--;){
			scanf("%s",w);
			scanf("%d%d",&a,&b);
			LL ans = 0;
			int u = 0 , nl = 0;
			static int ps[maxn],ln[maxn];
			rep(j,0,K-1){
				int v = w[j] - 'a';
				for(;~u && !tr[u][v];u = fa[u]);
				if(u == -1) nl = 0 , u = 0;
				else nl = min(nl + 1 , len[u] + 1) , u = tr[u][v];
				ps[j] = u , ln[j] = nl;
			}
			rep(i,a,b) if(r[i] - l[i] + 1 <= ln[r[i]]){
				int u = ps[r[i]];
				per(j,lim-1,0) if(len[f[j][u]] >= r[i] - l[i] + 1)
					u = f[j][u];
				ans += sm[u];
			}
			printf("%lld\n",ans);
		}
	}
}

CF666E Forensic Examination

給出一個串SS和字符串數組{Ti}\{ T_i\},每次詢問Spl,prS_{p_l,p_r}TlirT_{l\leq i\leq r}中的哪個數組中出現次數最多,多解輸出最靠前的那個。

SSTiT_i的廣義後綴自動機,把TiT_i的每個前綴在自動機上對應的點上插入ii,然後線段樹合併上傳。
每次詢問就是定位串之後簡單線段樹查詢即可。
AC Code\mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 2000005
#define lim 20
#define maxc 26
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

char Start;

int n,m;
char S[maxn],T[maxn];

int fa[maxn],tr[maxn][maxc],len[maxn],rt[maxn],pos[maxn],tot,last,f[lim][maxn];
vector<int>G[maxn];
void ins(int c,bool debug = 0){
	if(tr[last][c]){
		int p = last , q = tr[last][c];
		if(len[q] != len[p] + 1){
			int v=++tot;
			memcpy(tr[v],tr[q],sizeof tr[q]);
			fa[v]=fa[q],len[v]=len[p]+1;
			for(;~p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
			fa[q] = v;
		}
	}
	else{
		int u=++tot,p=last,q;
		len[u]=len[p]+1;
		for(;~p && !tr[p][c];p=fa[p]) tr[p][c]=u;
		if(p==-1) fa[u]=0;
		else if(len[q=tr[p][c]]==len[p]+1) fa[u]=q;
		else{
			int v=++tot;
			memcpy(tr[v],tr[q],sizeof tr[q]);
			fa[v]=fa[q],len[v]=len[p]+1;
			for(;~p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
			fa[q] = fa[u] = v;
		}
	}
}

#define maxp maxn *10
int lc[maxp],rc[maxp],mx[maxp],mxp[maxp];
void upd(int u){
	if(mx[lc[u]] >= mx[rc[u]]) mx[u] = mx[lc[u]] , mxp[u] = mxp[lc[u]];
	else mx[u] = mx[rc[u]] , mxp[u] = mxp[rc[u]];
}
void ins(int &u,int l,int r,int p){
	if(!u) u = ++tot , mxp[u] = l;
	if(l==r) return (void)(mx[u]++);
	int m = l+r>>1;
	p <= m ? ins(lc[u],l,m,p) : ins(rc[u],m+1,r,p);
	upd(u);
}
void merge(int &u,int l,int r){
	if(!l || !r) return (void)(u=l+r);
	u = ++tot;
	if(lc[l] || rc[l]){
		merge(lc[u],lc[l],lc[r]),merge(rc[u],rc[l],rc[r]);
		upd(u);
	}
	else{
		mx[u] = mx[l] + mx[r] , mxp[u] = mxp[l];
	}
}
pair<int,int> qry(int u,int l,int r,int ql,int qr){
	if(ql>r||l>qr||!u||ql>qr) return make_pair(0,0x3f3f3f3f);
	if(ql<=l&&r<=qr) return make_pair(mx[u],mxp[u]);
	int m = l+r>>1;
	pair<int,int>r1 = qry(lc[u],l,m,ql,qr) , r2 = qry(rc[u],m+1,r,ql,qr);
	if(r1.first >= r2.first) return r1;
	else return r2;
}

void dfs(int u){
	for(int v:G[u])
		dfs(v),merge(rt[u],rt[u],rt[v]);
}

char End;

int main(){
	
//	cerr << (&End - &Start) / 1024 / 1024 << endl;

	scanf("%s",S+1);
	n = strlen(S+1);
	fa[0] = -1;
	rep(i,1,n) ins(S[i]-'a'),pos[i]=last=tr[last][S[i]-'a'];
	scanf("%d",&m);
	rep(i,1,m){
		scanf("%s",T+1);
		int L = strlen(T+1);
		last = 0;
		rep(j,1,L){
			ins(T[j]-'a',(j == 13 && i == 2)),last=tr[last][T[j]-'a'];
			ins(rt[last],1,m,i);
		}
	}
	rep(i,1,tot) G[fa[i]].push_back(i), f[0][i] =fa[i];
	rep(j,1,lim-1) rep(i,1,tot) f[j][i] = f[j-1][f[j-1][i]];
	dfs(0);
	int q;scanf("%d",&q);
	for(int l,r,pl,pr;q--;){
		scanf("%d%d%d%d",&l,&r,&pl,&pr);
		int u = pos[pr];
		per(i,lim-1,0) if(len[f[i][u]] >= pr-pl+1)
			u=f[i][u];
		pair<int,int>R = qry(rt[u],1,m,l,r);
		if(R.first == 0)R.second = l;
		printf("%d %d\n",R.second,R.first);
	}
}

CF1276F Asterisk Substrings

求將字符串ss的每一位分別替換爲一個新字符"""*"後,所有的字符串(包括ss)中所有子串的並的本質不同串的個數。

注意到答案是ss,ss*,s*s,sts*t,*,\emptyset五種構成的。
而除了sts*t以外都可以簡單計算。
對於sts*t,發現對於endpos\texttt{endpos}相同的ss,可選的tt都是一樣的。
所以想到在後綴自動機上每個節點求出它作爲ss時可選的tt的個數。
如果ssendpos\texttt{endpos}是集合t1,t2,t3t_1,t_2,t_3,那麼可選的tt的個數就是所有的s[t1+2:],s[t2+2:],s[t3+2:]s[t_1+2:],s[t_2+2:],s[t_3+2:]也就是endpos\texttt{endpos}後移兩位後的所有後綴的子串的並的不同串的個數。
計算多個後綴的子串的並的不同串個數是一個可以用後綴數組解決的經典問題,將後綴排序後答案即爲所有後綴的長度減去相鄰後綴的LCP\texttt{LCP}
fail\texttt{fail}樹上set\texttt{set}啓發式合併即可簡單維護。

突然意識到既然複雜度是nlog2n的那我爲什麼不全程哈希求後綴數組和height呢
於是我們開始思考能不能不用後綴數組。

計算多個後綴的子串的並的不同串個數是一個可以用後綴自動機解決的經典問題,對反串建立後綴自動機後將需要求並的後綴在自動機上的點到根求個鏈並長度即爲答案(注意到在反串的後綴自動機中一個後綴一定是它所在點的最長串,所以不需要扣任何細節。),求鏈並可以用線段樹維護,具體是因爲把所有點按dfs\texttt{dfs}序排序後,鏈併爲所有點的深度和減去相鄰點的lca\texttt{lca}的深度和,那麼按dfs\texttt{dfs}序建線段樹就可以用線段樹合併維護答案,左右兒子合併時只需要求左邊的答案加右邊的答案減去左邊最靠右節點和右邊最靠左節點的lca\texttt{lca}深度。
所以我們就得到了只用後綴自動機的O(nlogn)O(n\log n)做法。怎麼感覺後綴自動機吊打後綴數組不留一點餘地的。

AC Code\mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 200005
#define maxc 26
#define lim 19
#define LL long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n;
char S[maxn];
LL ans;

int st[maxn],tim,mn[lim][maxn<<1],dep[maxn],lg[maxn<<1];
int Lca(int u,int v){// u ,v is dfn
	if(!u || !v) return 0; 
	if(u > v) swap(u,v);
	int t = lg[v-u+1];
	return dep[mn[t][u]] < dep[mn[t][v-(1<<t)+1]] ? mn[t][u] : mn[t][v-(1<<t)+1];
}

#define maxp maxn * 30
int rt[maxn],lc[maxp],rc[maxp],ml[maxp],mr[maxp],tot;
LL sm[maxp];
void upd(int u){
	sm[u] = sm[lc[u]] + sm[rc[u]] - dep[Lca(ml[rc[u]] , mr[lc[u]])];
	ml[u] = ml[lc[u]] ? ml[lc[u]] : ml[rc[u]];
	mr[u] = mr[rc[u]] ? mr[rc[u]] : mr[lc[u]];
}
void ins(int &u,int l,int r,int p){
	if(!u) u = ++tot;
	if(l==r) return (void)(ml[u]=mr[u]=p,sm[u]=dep[mn[0][p]]);
	int m =l+r>>1;
	p <= m ? ins(lc[u],l,m,p) : ins(rc[u],m+1,r,p);
	upd(u);
}
void merge(int &u,int l,int r){
	if(!l||!r) return (void)(u=l+r);
	u = ++tot;
	if(lc[l] || rc[l]){
		merge(lc[u],lc[l],lc[r]) , merge(rc[u],rc[l],rc[r]);
		upd(u);
	}
	else{
		// no else!
		assert(0);
	}
}

struct SAM{
	int tr[maxn][maxc] , fa[maxn] , len[maxn] , pos[maxn] , tot , last;
	vector<int>G[maxn];
	SAM(){
		memset(tr,0,sizeof tr);
		len[0] = tot = last = 0;
		fa[0] = -1; 
	}
	void ins(int c){
		int u = ++tot , p = last , q;
		len[last = u] = len[p] + 1;
		for(;~p && tr[p][c] == 0;p = fa[p]) tr[p][c] = u;
		if(p==-1) fa[u]=0;
		else if(len[q = tr[p][c]] == len[p] + 1) fa[u] = q;
		else {
			int v = ++tot;
			memcpy(tr[v],tr[q],sizeof tr[v]);
			fa[v] = fa[q] , len[v] = len[p] + 1;
			for(;~p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
			fa[u] = fa[q] = v;
		}
	}
	void dfs(int u){
		mn[0][st[u] = ++tim] = u;
		for(int v:G[u]){
			dep[v] = dep[u] + len[v] - len[u];
			dfs(v);
			mn[0][++tim] = u;
		}
	}
	void dfs2(int u){
		for(int v:G[u])
			dfs2(v),merge(rt[u],rt[u],rt[v]);
	}
	void BuildR(){
		rep(i,1,tot) G[fa[i]].push_back(i);
		dfs(0);
		rep(j,1,lim-1) rep(i,1,tim-(1<<j)+1) 
			mn[j][i] = dep[mn[j-1][i]] < dep[mn[j-1][i+(1<<j-1)]] ? mn[j-1][i] : mn[j-1][i+(1<<j-1)];
		rep(i,2,tim) lg[i] = lg[i>>1] + 1;
	}
	void BuildS(){
		rep(i,1,tot) G[fa[i]].push_back(i);
		dfs2(0);
		rep(i,1,tot)
			ans += 1ll * (len[i] - len[fa[i]]) * sm[rt[i]];
	}
}SA,RA;

int main(){
	scanf("%s",S);
	n = strlen(S);
	rep(i,0,n-1) SA.ins(S[i] - 'a') , SA.pos[i] = SA.last , ans += (i+1) - SA.len[SA.fa[SA.last]],(i == n-2) && (ans *= 2 );
	per(i,n-1,0) RA.ins(S[i] - 'a') , RA.pos[i] = RA.last , (i >= 1) && (ans += RA.len[RA.last] - RA.len[RA.fa[RA.last]]);
	ans += 2;
	RA.BuildR();
	rep(i,0,n-3)
		ins(rt[SA.pos[i]] , 1 , tim , st[RA.pos[i+2]]);
	SA.BuildS();
	printf("%lld\n",ans);
}

CF1063F String Journey

將串SS分解成u1+t1+u2+t2+....tk+uk+1u_1+t_1+u_2+t_2+....t_k+u_{k+1}的形式,其中tt需要額外滿足ti+1t_{i+1}tit_i的真子串。
求最大的kk

考慮把tt從小到大也就是從後往前考慮。
發現最優解一定可以是ti=i|t_i| = i的形式。

那麼之後有兩種做法:
做法一:
考慮到答案是n\sqrt n級別的。
那麼我們枚舉答案,然後枚舉所有長度爲答案的子串,看這個字符串刪去第一個或最後一個字符後的字符串有沒有在後面長度爲當前枚舉答案1-1的方案中出現過,如果出現過,那麼這個字符串加在開頭就可以構成長度爲答案的tt序列。
時間複雜度O(nn)O(n\sqrt n)
簡單哈希即可。
(事實證明這是個垃圾算法,要寫bitset哈希才能卡過(4000ms),可以被hackhack,但是隨機數據下可能很難撞。)
Code\mathcal Code

#include<bits/stdc++.h>
#define maxn 500005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define S 131
#define LL unsigned long long
using namespace std;

char s[maxn];
LL hs[maxn];
int n;
bitset<maxn>f[2];
#define M 7000007
/*
struct Table{
	int info[M],Prev[maxn],tot;
	LL val[maxn];
	void clear(){
		rep(i,1,tot) info[val[i] % M] = 0;
		tot = 0;
	}
	void ins(LL s){
		int t = s % M;
		val[++tot] = s , Prev[tot] = info[t] , info[t] = tot;
	}
	int qry(LL s){
		for(int i=info[s%M];i;i=Prev[i])
			if(s == val[i])
				return 1;
		return 0;
	}
}Map;*/

bitset<M>P;

int main(){//freopen("1.in","r",stdin);
	scanf("%d",&n);
	scanf("%s",s+1);
	reverse(s+1,s+n+1);
	int now = 1, pre = 0 , ans = 1 , sm = 1;
	rep(i,1,n) f[now][i] = 1;
	for(ans=2;;ans++){
		swap(now,pre);
		f[now].reset();
		rep(i,1,n-ans+2) hs[i] = (hs[i] * S + s[i+ans-2]) % M;
		P.reset();
		rep(i,sm,n-ans+1){
			if(P[hs[i]]|| (hs[i+1] != hs[i] && P[hs[i+1]]))
				f[now][i+ans-1] = 1;
			if(f[pre][i])
				P[hs[i-ans+2]] = 1;
		}
		if(f[now].none()){
			printf("%d\n",ans-1);
			return 0;
		}
		sm += ans;
	}	
}

第二種做法就是不枚舉答案,直接遞推。
fif_i爲前ii個字符,第ii個字符被選中的最多段數。
這個轉移有點奇妙:
fi=maxjifis[jfi+2>j]s[ifi+1,i]min(fj+1,fi)f_i = \max_{j \leq i - f_i \wedge s[j-f_i+2 -> j]是s[i-f_i+1,i]去掉第一個字符或去掉最後一個字符} \min(f_j + 1,f_i)
考慮從大到小枚舉fif_i,然後判定存不存在這樣的jj同時滿足fj+1fif_j+1 \geq f_ijifij \leq i-f_i以及是某個串。
最後一個條件說明jj在後綴樹是s[ifi+1,i1]s[i-f_i+1,i-1]s[ifi+2,i]s[i-f_i+2,i]的一個endpos\texttt{endpos}
寫子樹查詢即可。
第一個和第二個條件可以被一個性質解決:
對於iii+1i+1,ifii+1fi+1i-f_i \leq i+1-f_{i+1}
因爲一定有fifi+11f_i \geq f_{i+1}-1,這個不等式是顯然的。
所以我們可以得到ifii-f_i單調不降,可以直接每次加入ifii-f_i這個位置來滿足jifij \leq i-f_i
所以fi+1fi+1f_{i+1} \leq f_i + 1,我們可以每次讓fif_{i}fi1+1f_{i-1}+1開始往下枚舉,總枚舉次數是O(n)O(n)的。
所以我們就可以在O(nlogn)O(n \log n)的複雜度內解決此題。

CF906E Reverses

給你兩個長度爲 nn 的字符串 sstt。你可以翻轉 tt 的若干個不相交的區間,要求最終 sstt 相同。問你最少翻轉幾個區間,並輸出方案。

將兩個字符串交替寫下後就轉化爲了求最少偶迴文串劃分。
最小回文分割可以用迴文樹+border+border理論優化爲O(nlogn)O(n \log n)

AC Code\mathcal AC \ Code

#include<bits/stdc++.h>
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

#define maxn 1000005
#define maxc 26
char s[maxn],S[maxn],T[maxn];
int n,f[maxn],g[maxn],pr[maxn];//g the location

int fa[maxn],tr[maxn][maxc],len[maxn],dif[maxn],anc[maxn],tot,last;
void ins(int n,int c){
	while(s[n] != s[n-len[last]-1]) last = fa[last];
	if(!tr[last][c]){
		len[++tot] = len[last] + 2;
		int u = fa[last];
		while(s[n] != s[n-len[u]-1]) u = fa[u];
		fa[tot] = tr[u][c];
		dif[tot] = len[tot] - len[fa[tot]];
		anc[tot] = (dif[tot] == dif[fa[tot]] ? anc[fa[tot]] : fa[tot]);
		tr[last][c] = tot;
	}
	last = tr[last][c];
}

int main(){
	scanf("%s",S);
	scanf("%s",T);
	n=strlen(S);
	rep(i,0,n-1) s[i<<1|1] = S[i] , s[i+1<<1] = T[i];
	s[0] = '#';
	fa[0] = fa[1] = tot = 1;len[1] = -1;
	rep(i,1,n<<1){
		ins(i,s[i]-'a');
		f[i] = 0x3f3f3f3f;
		for(int u=last;u;u=anc[u]){
			g[u] = i - len[anc[u]] - dif[u];
			if(dif[u] == dif[fa[u]]) 
				if(f[g[fa[u]]] < f[g[u]])
					g[u] = g[fa[u]];
			if(!(i&1)) if(f[g[u]] + 1 < f[i])
				f[i] = f[g[u]] + 1 , pr[i] = g[u];
		}
		if(s[i] == s[i-1])
			if(f[i-2] < f[i])
				f[i] = f[i-2] , pr[i] = i-2;
	}
	if(f[n<<1] >= 0x3f3f3f3f){
		puts("-1");
		return 0;
	}
	printf("%d\n",f[n<<1]);
	for(int u=n<<1;u;u=pr[u])
		if(pr[u]/2+1 != u/2)
			printf("%d %d\n",pr[u]/2+1,u/2);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章