乱搞过【模板】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;
}

posted @ 2022-06-06 16:16  CHiSwsz  阅读(36)  评论(0编辑  收藏  举报