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