1. 程式人生 > 其它 >亂搞過【模板】Runs 的嘗試

亂搞過【模板】Runs 的嘗試

前言

眾所周知,字串有極多的 Theory,根本學不完(就算學完了過兩年 WC 上又會出來一個)。所以乾脆不學,正所謂高階的食材往往只需要簡單的烹飪。

P6656 【模板】Runs

Lyndon Theory 實在太噁心,所以嘗試不學 Runs 做這題。

考慮模仿這題的做法,對於一組 Run \((i,j,p)\),我們先列舉 \(p\),並且每隔 \(p\) 放一個點,用一個字尾資料結構快速查詢 LCP 和 LCS,因為週期為 \(p\) 的 Run 長度至少為 \(2p\),所以一定經過兩個點。

有了上面的東西,再手玩一下,很容易就可以求出一些可能成為 Run 的極長區間 \((i,j,p)\)

,但這裡只能保證 \(p\) 是一個週期,不能保證它是最小的,考慮到我們是從小到大列舉 \(p\),我們只用知道之前有沒有計算到區間 \((i,j)\) 就可以啦,這可以用一個 std::map 實現。

這樣就得到了一個 \(\mathcal{O}(n\log^2 n)\) 的做法,嘗試優化,首先可以把一對 \((i,j)\) 壓起來用 std::unordered_map,然後如果字尾資料結構那裡用的是 sam 的話,查詢 LCA 的時候可以使用尤拉序來搞。這裡我使用的是 sam,時間 \(\mathcal{O}(n\log n)\)

後來才發現 std::unordered_map 也很慢,還不如一開始就存下來,之後再 sort

去掉。

#include<iostream>
#include<stdio.h>
#include<ctype.h>
#include<vector>
#include<string.h>
#include<unordered_map>
#include<algorithm>
#define N 1000005
#define fi first
#define se second
using namespace std;
inline int read(){
	int x=0,f=0; char ch=getchar();
	while(!isdigit(ch)) f|=(ch==45),ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?-x:x;
}
struct SuffixAutoMation{
	int tail,pool,nxt[2*N][26],fail[2*N],len[2*N],dep[2*N],euler[4*N][20],in[2*N],euler0;
	vector<int> G[2*N];
	inline void init(){tail=pool=1;fail[1]=0;}
	inline int insert(int c){
		len[++pool]=len[tail]+1;
		int p=tail;tail=pool;
		for(;p && !nxt[p][c];p=fail[p]) nxt[p][c]=tail;
		if(!p){fail[tail]=1;return tail;}
		int q=nxt[p][c];
		if(len[p]+1==len[q]){fail[tail]=q;return tail;}
		len[++pool]=len[p]+1,fail[pool]=fail[q];
		memcpy(nxt[pool],nxt[q],sizeof(nxt[q]));
		fail[tail]=fail[q]=pool;
		for(;nxt[p][c]==q;p=fail[p]) nxt[p][c]=pool;
		return tail;
	}
	void dfs(int u){
		euler[++euler0][0]=u;in[u]=euler0;
		for(int v:G[u]){
			dep[v]=dep[u]+1,dfs(v);
			euler[++euler0][0]=u;
		}
	}
	inline void build(){
		for(int i=2;i<=pool;++i) G[fail[i]].push_back(i);
		dep[1]=1,dfs(1);
		for(int j=1;j<=19;++j){
			for(int i=1;i+(1<<j)-1<=euler0;++i){
				euler[i][j]=dep[euler[i][j-1]]<dep[euler[i+(1<<j-1)][j-1]]?euler[i][j-1]:euler[i+(1<<j-1)][j-1];
			}
		}
	}
	inline int LCA(int x,int y){
		if(in[x]>in[y]) swap(x,y);
		int k=__lg(in[y]-in[x]+1);
		return len[dep[euler[in[x]][k]]<dep[euler[in[y]-(1<<k)+1][k]]?euler[in[x]][k]:euler[in[y]-(1<<k)+1][k]];
	}
}sam1,sam2;
int n,id1[N],id2[N];
char s[N];
inline int LCS(int x,int y){
	if(x<1 || y<1) return 0;
	return sam1.LCA(id1[x],id1[y]);
}
inline int LCP(int x,int y){
	if(x>n || y>n) return 0;
	return sam2.LCA(id2[x],id2[y]);
}
unordered_map<long long,int> mp;
vector<pair<pair<int,int>,int> > ans;
inline void work(int len){
	for(int l=1,r;l+len<=n;l=r){
		r=l-len;
		while(r+2*len<=n && LCP(r+len,r+2*len)==len) r+=len;
		int L,R;r+=2*len;
		L=LCS(l-1,l+len-1);
		R=LCP(r,r-len);
		long long ha=1LL*(l-L)*1000000+1LL*(r+R-1);
		if(r==l+len){
			if(L+R>=len){
				if(!mp[ha]){
					mp[ha]=1;
					ans.push_back({{l-L,r+R-1},len});
				}
			}
		}
		else{
			if(!mp[ha]){
				mp[ha]=1;
				ans.push_back({{l-L,r+R-1},len});
			}
		}
	}
}
int main(){
	sam1.init(),sam2.init();
	scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;++i) id1[i]=sam1.insert(s[i]-'a');
	for(int i=n;i>=1;--i) id2[i]=sam2.insert(s[i]-'a');
	sam1.build(),sam2.build();
	for(int i=1;i<=n;++i) work(i);
	sort(ans.begin(),ans.end());
	printf("%d\n",ans.size());
	for(auto v:ans) printf("%d %d %d\n",v.fi.fi,v.fi.se,v.se);
	return 0;
}

這時候我們就就得到了 60 分,這個無*的出題人居然把空間開的只剩 256MB!

不過這可難不倒我們,只需要把 sam 換成 SA 就好了。

#include<iostream>
#include<stdio.h>
#include<ctype.h>
#include<vector>
#include<string.h>
#include<unordered_map>
#include<algorithm>
#define N 1000005
#define fi first
#define se second
using namespace std;
inline int read(){
	int x=0,f=0; char ch=getchar();
	while(!isdigit(ch)) f|=(ch==45),ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?-x:x;
}
int n;
char s[N];
int rk[N*2],sa[N],ork[N*2],cnt[N],id[N],lcp[N][20],lcs[N][20],height[N],P[N],S[N];
inline void suffixArray(int flag){
	int z=300,p=0;
	memset(cnt,0,sizeof(cnt));
	memset(rk,0,sizeof(rk));
	for(int i=1;i<=n;++i) cnt[rk[i]=s[i]]++;
	for(int i=1;i<=z;++i) cnt[i]+=cnt[i-1];
	for(int i=n;i>=1;--i) sa[cnt[rk[i]]--]=i;
	for(int w=1;p^n;w<<=1,z=p){
		memset(cnt,0,sizeof(cnt)),p=0;
		for(int i=n-w+1;i<=n;++i) id[++p]=i;
		for(int i=1;i<=n;++i) if(sa[i]>w) id[++p]=sa[i]-w;
		for(int i=1;i<=n;++i) cnt[rk[i]]++;
		for(int i=1;i<=z;++i) cnt[i]+=cnt[i-1];
		for(int i=n;i>=1;--i) sa[cnt[rk[id[i]]]--]=id[i];
		memcpy(ork,rk,sizeof(ork)),p=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]?p:++p);
	}
	if(!flag) for(int i=1;i<=n;++i) P[i]=rk[i];
	else for(int i=1;i<=n;++i) S[i]=rk[i];
}
inline void getHeight(){
	for(int i=1,k=0;i<=n;++i){
		if(k) --k;
		while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
		height[rk[i]]=k;
	}
}
inline int LCP(int x,int y){
	if(x>n || y>n) return 0;
	x=P[x],y=P[y];
	if(x>y) swap(x,y);
	x++;
	int k=__lg(y-x+1);
	return min(lcp[x][k],lcp[y-(1<<k)+1][k]);
}
inline int LCS(int x,int y){
	if(x<1 || y<1) return 0;
	y=S[n-y+1],x=S[n-x+1];
	if(x>y) swap(x,y);
	x++;
	int k=__lg(y-x+1);
	return min(lcs[x][k],lcs[y-(1<<k)+1][k]);
}
unordered_map<long long,int> mp;
vector<pair<pair<int,int>,int> > ans;
inline void work(int len){
	for(int l=1,r;l+len<=n;l=r){
		r=l-len;
		while(r+2*len<=n && LCP(r+len,r+2*len)==len) r+=len;
		int L,R;r+=2*len;
		L=LCS(l-1,l+len-1);
		R=LCP(r,r-len);
		long long ha=1LL*(l-L)*10000000+1LL*(r+R-1);
		if(r==l+len){
			if(L+R>=len){
				if(!mp[ha]){
					mp[ha]=1;
					ans.push_back({{l-L,r+R-1},len});
				}
			}
		}
		else{
			if(!mp[ha]){
				mp[ha]=1;
				ans.push_back({{l-L,r+R-1},len});
			}
		}
	}
}
int main(){
	scanf("%s",s+1);
	n=strlen(s+1);
	suffixArray(0),getHeight();
	for(int i=1;i<=n;++i) lcp[i][0]=height[i];
	reverse(s+1,s+n+1);
	suffixArray(1),getHeight();
	for(int i=1;i<=n;++i) lcs[i][0]=height[i];
	for(int j=1;j<20;++j){
		for(int i=1;i+(1<<j)-1<=n;++i){
			lcp[i][j]=min(lcp[i][j-1],lcp[i+(1<<j-1)][j-1]);
			lcs[i][j]=min(lcs[i][j-1],lcs[i+(1<<j-1)][j-1]);
		}
	}
	for(int i=1;i<=n;++i) work(i);
	sort(ans.begin(),ans.end());
	printf("%d\n",ans.size());
	for(auto v:ans) printf("%d %d %d\n",v.fi.fi,v.fi.se,v.se);
	return 0;
}

然後就有了 70 分,TLE 了,這個無*的出題人居然把時間開到線性做法才能過!

然後我就沒辦法了,只能在心中默默問候出題人,嘗試以失敗告終。

不過正常的字串題一般都不會開到 \(10^6\) 的,也就是說上面的做法一般是可以用的,我們不用學 Lyndon Theory 了!!!

下面就是一個例子。

uoj429【集訓隊作業2018】串串劃分

這題前面的轉化相當的巧妙。

看到集訓隊作業先轉化模型:兩個限制看起來是有關聯的,因為兩個相鄰的串相等的話,如果把他們合到一起就變成了一個迴圈串。

相鄰串不能相等,如果有這個限制的話 DP 必不能優化到 \(\mathcal{O}(n)\),因為要處理這個限制需要記錄上一次選了哪。所以考慮容斥,現在相鄰串可以相等了,但好像還是沒什麼用,需要把它和第二個限制聯絡起來。如果現在又 \(k\) 個串相鄰相等,把它們看作一個整體,因為每個串都不迴圈,所以整個大串就是一個最多有 \(k\) 個迴圈節的串,這對這個方案的貢獻是 \((-1)^{k-1}\),所以我們 DP 的時候可以把這 \(k\) 個串一起考慮,也就是說我們加入一個串時,它的貢獻是負一的它的最大迴圈節數減一次方。

但這個還是不好做,於是繼續轉化,發現我們只關心 \(k\) 的奇偶,\(k\) 是偶數的時候才會有 \(-1\)。而當 \(k\) 是偶數,這個串 \(S\) 肯定能表示為 \(AA\) 的形式,這個非常好 check。所以我們就有了一個 \(\mathcal{O}(n^2)\) 的做法。

繼續優化,發現一個結論,我們稱能表示為 \(AA\) 的串為平方串,\(S\) 為平方串,則 \(S^i\) 也是平方串,這個非常顯然。我們稱不能表示為 \(BB\) 的平方串 \(S\) 為基本平方串,其中 \(B\) 為平方串,說人話就是不能拆成兩個相同的平方串的平方串。還有一個結論,在一個串 \(S\) 中,\(A,B,C\)\(S\) 的子串,左端點相同且都是基本平方串,設 \(|A|<|B|<|C|\),則有 \(|C|\ge|A|+|B|\),這說明以一個位置為左端點的基本平方串數量是 \(\log\) 的,那整個串就是 \(n\log n\) 的。這個證明過(wo)於(bu)繁(hui)雜(zheng),所以這裡不寫了。

所以所有的基本平方串理論上是可以全部找出的,因為其他的平方串都可以用基本平方串自己接自己得到,所以 DP 就很容易優化成一個 \(\log\),至於怎麼優化,亂搞即可。

現在問題來到怎麼求所有的基本平方串,發現如果我們知道所有的 Run \((i,j,p)\),基本平方串就是 \(S[k,k+2p-1],k\in[i,j-2p+1]\)。所以套上一個求 Runs 就行啦!這裡 \(n\)\(2\cdot 10^5\),上面的亂搞做法是可以過的。

然後我就喜提 uoj 最快解倒數第一了!

#include<iostream>
#include<stdio.h>
#include<ctype.h>
#include<vector>
#include<string.h>
#include<unordered_map>
#include<algorithm>
#include<set>
#define N 200005
#define fi first
#define se second
using namespace std;
inline int read(){
	int x=0,f=0; char ch=getchar();
	while(!isdigit(ch)) f|=(ch==45),ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?-x:x;
}
struct SuffixAutoMation{
	int tail,pool,nxt[2*N][26],fail[2*N],len[2*N],dep[2*N],euler[4*N][20],in[2*N],euler0;
	vector<int> G[2*N];
	inline void init(){tail=pool=1;fail[1]=0;}
	inline int insert(int c){
		len[++pool]=len[tail]+1;
		int p=tail;tail=pool;
		for(;p && !nxt[p][c];p=fail[p]) nxt[p][c]=tail;
		if(!p){fail[tail]=1;return tail;}
		int q=nxt[p][c];
		if(len[p]+1==len[q]){fail[tail]=q;return tail;}
		len[++pool]=len[p]+1,fail[pool]=fail[q];
		memcpy(nxt[pool],nxt[q],sizeof(nxt[q]));
		fail[tail]=fail[q]=pool;
		for(;nxt[p][c]==q;p=fail[p]) nxt[p][c]=pool;
		return tail;
	}
	void dfs(int u){
		euler[++euler0][0]=u;in[u]=euler0;
		for(int v:G[u]){
			dep[v]=dep[u]+1,dfs(v);
			euler[++euler0][0]=u;
		}
	}
	inline void build(){
		for(int i=2;i<=pool;++i) G[fail[i]].push_back(i);
		dep[1]=1,dfs(1);
		for(int j=1;j<=19;++j){
			for(int i=1;i+(1<<j)-1<=euler0;++i){
				euler[i][j]=dep[euler[i][j-1]]<dep[euler[i+(1<<j-1)][j-1]]?euler[i][j-1]:euler[i+(1<<j-1)][j-1];
			}
		}
	}
	inline int LCA(int x,int y){
		if(in[x]>in[y]) swap(x,y);
		int k=__lg(in[y]-in[x]+1);
		return len[dep[euler[in[x]][k]]<dep[euler[in[y]-(1<<k)+1][k]]?euler[in[x]][k]:euler[in[y]-(1<<k)+1][k]];
	}
}sam1,sam2;
int n,id1[N],id2[N];
char s[N];
inline int LCS(int x,int y){
	if(x<1 || y<1) return 0;
	if(x>n || y>n) return 0;
	return sam1.LCA(id1[x],id1[y]);
}
inline int LCP(int x,int y){
	if(x<1 || y<1) return 0;
	if(x>n || y>n) return 0;
	return sam2.LCA(id2[x],id2[y]);
}
unordered_map<long long,int> mp;
vector<pair<pair<int,int>,int> > run;
inline void work(int len){
	for(int l=1,r;l+len<=n;l=r){
		r=l-len;
		while(r+2*len<=n && LCP(r+len,r+2*len)==len) r+=len;
		int L,R;r+=2*len;
		L=LCS(l-1,l+len-1);
		R=LCP(r,r-len);
		long long ha=1LL*(l-L)*1000000+1LL*(r+R-1);
		if(r==l+len){
			if(L+R>=len){
				if(!mp[ha]){
					mp[ha]=1;
					run.push_back({{l-L,r+R-1},len});
				}
			}
		}
		else{
			if(!mp[ha]){
				mp[ha]=1;
				run.push_back({{l-L,r+R-1},len});
			}
		}
	}
}
inline bool cmp(pair<pair<int,int>,int> x,pair<pair<int,int>,int> y){
	return x.se>y.se;
}
#define mo 998244353
vector<int> sq[N];
vector<pair<int,int> > add[N];
int f[N],sum[N];
inline void red(int &x){x>=mo?x-=mo:0;}
int main(){
	sam1.init(),sam2.init();
	scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;++i) id1[i]=sam1.insert(s[i]-'a');
	for(int i=n;i>=1;--i) id2[i]=sam2.insert(s[i]-'a');
	sam1.build(),sam2.build();
	for(int i=1;i<=n;++i) work(i);
	sort(run.begin(),run.end(),cmp);
	for(auto v:run){
		for(int i=v.fi.fi+2*v.se-1;i<=v.fi.se;++i){
			sq[i].push_back(i-2*v.se+1);
		}
	}
	f[0]=sum[0]=1;
	for(int i=1;i<=n;++i){
		f[i]=sum[i-1];
		int p=0;
		for(int j:sq[i]){
			int tmp=f[j-1];
			while(p<add[i].size() && add[i][p].fi==j) red(tmp+=add[i][p].se),p++;
			int k=i+(i-j+1);
			if(LCP(j,i+1)>=i-j+1) add[k].push_back({i+1,tmp});
			red(f[i]+=mo-2*tmp%mo);
		}
		red(sum[i]=sum[i-1]+f[i]);
	}
	cout<<f[n];
	return 0;
}