专项测试 字符串2
A. 肃正协议
因为区间不交且每一次的都为上一次的真子串,所以答案不超过 \(O(\sqrt{n})\)
设 \(f_{i}\) 表示以 \(i\) 为起点的最长答案
根据定义发现一条性质 \(f_i \leq f_{i+1}+1\) 相当于最多往答案的序列添加一个字符
那么可以去倒序枚举每一个字符,再从大到小枚举每一种长度,枚举的起点就是 \(f_{i+1}+1\)
如果当前的长度 \(j\) 合法,那么一定存在一个开头在 \(i+j\) 往后的字符串为当前字符串的子串
不难发现最优情况下答案的序列为 \(6,5,4,3,2,1\) 这种类型的
因此只需要判断 \([i,i+j-2]\) 或 \([i+1,i+j-1]\) 是否在之前的合法串中出现过即可
对于合法串可以用 \(map\) 来维护
合法串的起点为 \(i+len-1\) 每次 \(i\) 会减少 \(1\) 而 \(len\) 至多增加 \(1\)
所以起始点时单调不增的
因此当一个合法串的起点可以被加入时当且仅当当前枚举的长度减少时
时间复杂度上界 \(O(n\sqrt{n})\) 主要是在维护合法串的哈希
还有一种 \(O(n\log{n})\) 的做法
将整个串翻过来建立 \(SAM\) 正序枚举每个点
\(f_i\) 的定义变成以 \(i\) 为结尾的答案长度,那么 \(f_i \leq f_{i-1}+1\) 和上面的一样
我们在需要在 \(SAM\) 上定位出两个点来判定是否存在向上面一样的两种情况
需要保证两个点的深度和当前点之差大于当前的答案
每次查询子树的最值和当前的答案进行比较即可
还是在每次长度减少的时候加入贡献
Code
#include<bits/stdc++.h>
#define uint unsigned long long
#define rint signed
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,ans;
int f[500010];
uint HASH[500010],pw[500010],tHASH;
char st[500010];
unordered_map<uint,bool> MP;
inline uint getHASH(int l,int r){return HASH[r]-HASH[l-1]*pw[r-l+1];}
inline void add(int s){for(int i=f[s];i;i--){if(MP.count(getHASH(s,s+i-1))) return ;MP[getHASH(s,s+i-1)]=1;}}
signed main(){
#ifdef LOCAL
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
//n=read();
scanf("%s",st+1);n=strlen(st+1);MP[0]=1;
pw[0]=1;for(int i=1;i<=n;i++) pw[i]=pw[i-1]*131;
for(int i=1;i<=n;i++) HASH[i]=HASH[i-1]*131+st[i]-'a'+1;
for(int i=n;i;i--){
for(int j=f[i+1]+1;j;add(i+j-1),j--){
if(j==1){f[i]=1;break;}
if(MP.count(getHASH(i,i+j-2))==1){f[i]=j;goto NXT;}
if(MP.count(getHASH(i+1,i+j-1))==1){f[i]=j;goto NXT;}
}
NXT:ans=max(ans,f[i]);
}
printf("%d\n",ans);
return 0;
}
B. 虚空恶魔
对于同本质相同的同一种子串,他们的贡献一定出在第一次出现和最后一次出现的时候
所以我们只用维护出每种子串第一次和最后一次出现的位置就行,可以用 \(SAM\)
然后跟长度判断两点是否距离超过 \(len\) 如果超过则可以贡献答案
Code
#include<bits/stdc++.h>
//#define int long long
#define rint signed
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,lst=1,tot=1;
long long ans;
int a[4000010],tax[4000010];
char st[4000010];
struct node{int len,fa,mx,mn,son[26];}t[4000010];
inline void extend(int c,int Ti){
int p=++tot,f=lst;lst=p;
t[p].len=t[f].len+1;t[p].mx=t[p].mn=Ti;
while(f&&!t[f].son[c]) t[f].son[c]=p,f=t[f].fa;
if(!f) return t[p].fa=1,void();
int x=t[f].son[c],y=++tot;
if(t[f].len+1==t[x].len) return t[p].fa=x,tot--,void();
t[y]=t[x];
t[y].len=t[f].len+1;t[x].fa=t[p].fa=y;
while(f&&t[f].son[c]==x) t[f].son[c]=y,f=t[f].fa;
}
signed main(){
#ifdef LOCAL
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
scanf("%s",st+1);n=strlen(st+1);
for(int i=1;i<=n;i++) extend(st[i]-'a',i);
for(int i=1;i<=tot;i++) tax[t[i].len]++;
for(int i=1;i<=tot;i++) tax[i]+=tax[i-1];
for(int i=1;i<=tot;i++) a[tax[t[i].len]--]=i;
for(int i=tot,x;i;i--){
x=a[i];
t[t[x].fa].mn=min(t[t[x].fa].mn,t[x].mn);
t[t[x].fa].mx=max(t[t[x].fa].mx,t[x].mx);
}
for(int i=1,L;i<=tot;i++) if(t[i].mx-t[i].mn!=0){
L=t[i].mx-t[i].mn;
L=min(t[i].mx-t[i].mn,t[i].len);
if(L) ans=max({ans,1ll*L*(n-t[i].mn),1ll*L*(t[i].mx-L+1-1)});
}
printf("%lld\n",ans);
return 0;
}
C. 高维入侵
重叠的部分一定是原串的一个 \(border\) 于是用 \(kmp\) 求出所有的 \(border\)
设 \(x_i\) 表示 \(|s|-border_i\) 即每一种可行转移的长度
要求的是不同的长度数量,那么问题就变成了 \(\sum{a_ix_i}\) 在 \([0,m-n]\) 范围内能够取到的不同取值的数量
是同余最短路的形式,以 \(x_1\) 为模数,对 \([0,x_1-1]\) 分别建点
对所有点都连上从 \(i\) 到 $(i+x_j) % x_1 $ 的边权为 \(x_j\) 的边
跑最短路,最后每个点代表的 \(dis_i\) 即为通过其他点的加和得到的余数为 \(i\) 的最小值
对答案的贡献为 \((m-dis_i)/mod+1\) ( \(mod\) 为模数)
复杂度 \(O(n^2\log{n})\)
根据 \(border\) 的性质,一定能够划分成 \(\log{n}\) 个等差数列
先考虑等差数列内部的贡献
设当前的序列为首项为 \(fir\) 公差为 \(d\) 长度为 \(len\)
每次都以 \(fir\) 为模数
不难发现每个等差序列能够构成若干个互相独立的环
环上最小的点一定不会被松弛,于是我们破环为链
根据在同余最短路上建的边,每个点只跟在链上距离小于等于 \(len-1\) 的点有连边
考虑这些连边的贡献
\(f_i + fir + d \times (j-i) \rightarrow f_j\) 拆一下式子可以直接单调队列做
再考虑不同的等差数列之间的贡献
这种情况下我们需要更换模数
设 \(f_i\) 为原来的模数下求得的最小值, \(g_i\) 为新的模数下求得的最小值
原来的模数为 \(pmod\) 现在的模数为 \(nmod\)
那么直接将 \(f_i\) 转移到 \(g_{f[i] \% nmod}\) 再对原来的模数跑一遍环形转移即可,处理方法一样
复杂度 \(O(n\log{n})\)
Code
#include<bits/stdc++.h>
#define int long long
#define rint signed
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,mod,ans;
int x[2000010],p;
int f[2000010],g[2000010];
int pos[2000010],trans[2000010],poi,ppoi;
int fail[2000010];
int que[2000010],h,t;
char st[2000010];
bool vis[2000010];
inline void changemod(int pmod,int nmod){
mod=nmod;
for(int i=0;i<nmod;i++) g[i]=inf,vis[i]=0;
for(int i=0;i<pmod;i++) g[f[i]%nmod]=min(g[f[i]%nmod],f[i]);
for(int i=0,j,mn;i<nmod;i++) if(!vis[i]){
j=i;poi=0;ppoi=0;mn=inf;
while(!vis[j]) vis[pos[++poi]=j]=1,j=(j+pmod)%mod;
for(int k=1;k<=poi;k++) mn=min(mn,g[pos[k]]);
for(int k=1;k<=poi;k++) if(g[pos[k]]==mn){
for(int l=k;l<=poi;l++) trans[++ppoi]=pos[l];
for(int l=1;l<k;l++) trans[++ppoi]=pos[l];
break;
}
for(int k=1;k<=poi;k++) pos[k]=trans[k];
mn=inf;
for(int k=1;k<=poi;k++){
g[pos[k]]=min(g[pos[k]],mn+k*pmod);
mn=min(mn,g[pos[k]]-k*pmod);
}
}
for(int i=0;i<nmod;i++) f[i]=g[i];
}
inline void solveseq(int fir,int d,int len){
for(int i=0;i<mod;i++) vis[i]=0;
for(int i=0,j,poi,mn,ppoi;i<mod;i++) if(!vis[i]){
j=i;poi=0;ppoi=0;mn=inf;
while(!vis[j]){
pos[++poi]=j;
vis[j]=1;
j=(j+d)%mod;
}
for(int k=1;k<=poi;k++) mn=min(mn,f[pos[k]]);
for(int k=1;k<=poi;k++) if(f[pos[k]]==mn){
for(int l=k;l<=poi;l++) trans[++ppoi]=pos[l];
for(int l=1;l<k;l++) trans[++ppoi]=pos[l];
break;
}
for(int k=1;k<=poi;k++) pos[k]=trans[k];
h=1,t=0;
for(int k=1;k<=poi;k++){
while(h<=t&&k-que[h]>=len) h++;
if(h<=t){
f[pos[k]]=min(f[pos[k]],fir+f[pos[que[h]]]-que[h]*d+k*d);
}
while(h<=t&&f[pos[k]]-k*d<f[pos[que[t]]]-que[t]*d) t--;
que[++t]=k;
}
}
}
signed main(){
#ifdef LOCAL
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
n=read(),m=read();scanf("%s",st+1);m-=n;
for(int i=2,j=0;i<=n;i++){
while(j&&st[i]!=st[j+1]) j=fail[j];
if(st[i]==st[j+1]) j++;
fail[i]=j;
}
int j=fail[n];while(j) x[++p]=n-j,j=fail[j];x[++p]=n;
for(int i=1;i<=2000000;i++) f[i]=inf;
for(int i=1,j=1;i<=p;i=j+1){
j=i;
while(j<p&&x[j+1]-x[j]==x[i+1]-x[i]) j++;
if(i!=1) changemod(mod,x[i]);
else mod=x[1];
if(i+1<=p) solveseq(x[i],x[i+1]-x[i],j-i+1);
}
for(int i=0;i<mod;i++) if(f[i]<=m) ans+=(m-f[i])/mod+1;
printf("%lld\n",ans);
return 0;
}

浙公网安备 33010602011771号