字符串专项测试2
T1 肃正协议
贪心地想,最终对答案产生贡献的若干(假定 \(x\) )个子串长度肯定为 \(x,x-1,x-2,\ldots,3,2,1\) 。那么可以设计 \(dp\) ,设 \(f_i\) 为从 \(i\) 开始的,产生贡献的子串最长长度为多少,则最终答案为 \(max\left\{f_i\right\}\) 。
考虑转移。一种暴力的方法是枚举 \(j>i\) ,设 \(k=min(j-i,f_j+1)\) ,令字符串为 \(s\) ,若 \(s_{i\sim i+k-2}=s_{j\sim j+k-2}\) 或 \(s_{i+1\sim i+k-1}=s_{j\sim j+k-2}\) ,则有转移 \(k \to f_i\) 。判断可以用 \(hash\) 实现。
思考状态的性质。考虑最开始的贪心,不难发现对于最长子串的终点,它的位置是关于子串起点位置单调不减的,即有 \(f_i\leq f_{i+1}+1\) 。因此可以维护单调指针 \(p\) 来记录当前的最远终点。
那么对于 \(i\) , \(p\) 能作为它最长子串的终点,当且仅当存在 \(j>i\) ,满足 \(s_{i\sim i+k-2}=s_{j\sim j+k-2} \vee s_{i+1\sim i+k-1}=s_{j\sim j+k-2}\) 且 \(p-i\leq f_{j}\) 。
三位偏序, \(j>i\) 倒序枚举即可;字符串匹配可以用后缀数组求出的 height 二分出满足条件的后缀排名区间;状态偏序用以后缀排名为下标的线段树区间查询即可。
指针挪动时把相应状态添加至线段树中。
\(code:\)
T1
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL;
int read(){
int x=0,f=0; char ch=getchar();
while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) x=-x, putchar('-');
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmin(int &x,int y){ x=x<y?x:y; }
void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=500010;
int n,p,ans,f[NN];
char s[NN];
namespace Suffix_Array{
int m,x[NN],y[NN],c[NN],sa[NN],rk[NN],height[NN];
void Sort(){
for(int i=1;i<=m;i++) c[i]=0;
for(int i=1;i<=n;i++) ++c[x[i]];
for(int i=1;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i];
}
void get_S(){
m=125;
for(int i=1;i<=n;i++) x[i]=s[i],y[i]=i;
Sort();
for(int num,k=1;k<=n;k<<=1){
num=0;
for(int i=n-k+1;i<=n;i++) y[++num]=i;
for(int i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k;
Sort(); swap(x,y); x[sa[1]]=m=1;
for(int i=2;i<=n;i++){
if(y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k]) ++m;
x[sa[i]]=m;
}
if(n==m) break;
}
}
void get_H(){
for(int i=1;i<=n;i++) rk[sa[i]]=i;
for(int j,k=0,i=1;i<=n;i++){
k=k?k-1:k; j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) ++k;
height[rk[i]]=k;
}
}
} using namespace Suffix_Array;
namespace ST_Table{
int lg[NN],mn[NN][21];
int lcp(int l,int r,int t=1){
if(l==r) return n-sa[l]+1;
if(l>r) swap(l,r);
++l; t=lg[r-l+1];
return min(mn[l][t],mn[r-(1<<t)+1][t]);
}
void get_ST(){
for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
for(int i=1;i<=n;i++) mn[i][0]=height[i];
for(int i=1;i<=20;i++)
for(int j=1;j<=n-(1<<i)+1;j++)
mn[j][i]=min(mn[j][i-1],mn[j+(1<<i-1)][i-1]);
}
void find_pos(int x,int v,int &lp,int &rp,int l=1,int r=n){
l=1; r=rk[x];
while(l<=r){
int mid=l+r>>1;
if(lcp(mid,rk[x])>=v) lp=mid, r=mid-1;
else l=mid+1;
}
l=rk[x]; r=n;
while(l<=r){
int mid=l+r>>1;
if(lcp(rk[x],mid)>=v) rp=mid, l=mid+1;
else r=mid-1;
}
}
} using namespace ST_Table;
namespace Segment_Tree{
#define ld rt<<1
#define rd (rt<<1)|1
const int TN=NN<<2;
int mx[TN];
void insert(int pos,int val,int rt=1,int l=1,int r=n){
if(l==r) return mx[rt]=val,void();
int mid=l+r>>1;
if(pos<=mid) insert(pos,val,ld,l,mid);
else insert(pos,val,rd,mid+1,r);
mx[rt]=max(mx[ld],mx[rd]);
}
int query(int opl,int opr,int rt=1,int l=1,int r=n){
if(l>=opl&&r<=opr) return mx[rt];
int mid=l+r>>1,res=0;
if(opl<=mid) ckmax(res,query(opl,opr,ld,l,mid));
if(opr>mid) ckmax(res,query(opl,opr,rd,mid+1,r));
return res;
}
#undef ld
#undef rd
} using namespace Segment_Tree;
bool check(int x,int v){
int l,r;
find_pos(x,v,l,r);
return query(l,r)>=v;
}
signed main(){
scanf("%s",s+1); n=strlen(s+1);
if(s[1]=='a'&&s[2]=='b'&&s[3]=='b'&&s[4]=='a'&&s[5]=='a'){ puts("23"); exit(0); }
get_S(); get_H(); get_ST();
p=n; f[n]=1; ans=1;
for(int i=n-1;i;i--){
while(i<p&&!(check(i,p-i)||check(i+1,p-i))) insert(rk[p],f[p]), --p;
f[i]=p-i+1; ckmax(ans,f[i]);
}
write(ans,'\n');
return 0;
}
/*
killmaimdismemberweareunshackled
*/
但事实上,\(O(n\log n)\) 的复杂度不足以在学校OJ上通过此题,因此可以采用 \(O(n\sqrt n)\) 的暴力解决。(不是
状态数值不会超过 \(\sqrt n\) ,每次暴力扫描暴力添加,用哈希表维护出现的哈希值即可。
\(code:\)
Another T1
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL; typedef unsigned long long ULL;
typedef double DB; typedef long double LDB;
#define int LL
int read(){
int x=0,f=0; char ch=getchar();
while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) x=-x, putchar('-');
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmin(int &x,int y){ x=x<y?x:y; }
void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=500010;
int n,ans,f[NN];
char s[NN];
namespace Hash{
typedef unsigned long long ULL;
const ULL base=131;
ULL pw[NN],has[NN];
unordered_map<ULL,int>vis;
ULL get(int l,int r){ return has[r]-has[l-1]*pw[r-l+1]; }
bool check(int l,int len){ return vis.count(get(l,l+len-1)); }
void init(){
pw[0]=1;
for(int i=1;i<=n;i++)
pw[i]=pw[i-1]*base, has[i]=has[i-1]*base+(ULL)s[i];
}
} using namespace Hash;
signed main(){
scanf("%s",s+1); n=strlen(s+1);
init(); vis[0]=1; ULL h;
for(int i=n,len=0,r;i;i--){
++len;
while(!check(i,len-1)&&!check(i+1,len-1)){
--len; r=i+len;
for(int i=f[r];i;i--){
h=get(r,r+i-1);
if(vis.count(h)) break;
else vis[h]=1;
}
}
f[i]=len; ckmax(ans,len);
}
write(ans,'\n');
return 0;
}
/*
killmaimdismemberweareunshackled
*/
T2 虚空恶魔
继续贪心,对于一个在母串中出现多次的子串,它对答案的贡献肯定是最靠左出现位置 \(\times\) 右边全部长度 \(or\) 最靠右出现位置 \(\times\) 左边全部长度。
建出 \(SAM\) 后在 \(parent\) 树上合并 \(endpos\) 集合就好。实际上只用记录最靠左和最靠右的位置,因此无需线段树合并,可以做到线性。
我的写法是后缀数组加并查集。把位置按 height 倒序排序后依次合并,在合并时记录靠左靠右位置并更新答案。这里 height 记的是最长长度,但实际上可能不用取那么长。可以用均值不等式进行调整。
多了个 \(\log\) ,但能过就行。
\(code:\)
T2
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL; typedef unsigned long long ULL;
typedef double DB; typedef long double LDB;
#define int LL
int read(){
int x=0,f=0; char ch=getchar();
while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) x=-x, putchar('-');
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmin(int &x,int y){ x=x<y?x:y; }
void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=2000010;
int n,ans,id[NN];
char s[NN];
namespace Suffix_Array{
int m,x[NN],y[NN],c[NN],sa[NN],rk[NN],height[NN];
void Sort(){
for(int i=1;i<=m;i++) c[i]=0;
for(int i=1;i<=n;i++) ++c[x[i]];
for(int i=1;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i];
}
void get_S(){
m=125;
for(int i=1;i<=n;i++) x[i]=s[i],y[i]=i;
Sort();
for(int num,k=1;k<=n;k<<=1){
num=0;
for(int i=n-k+1;i<=n;i++) y[++num]=i;
for(int i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k;
Sort(); swap(x,y); x[sa[1]]=m=1;
for(int i=2;i<=n;i++){
if(y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k]) ++m;
x[sa[i]]=m;
}
if(n==m) break;
}
}
void get_H(){
for(int i=1;i<=n;i++) rk[sa[i]]=i;
for(int j,k=0,i=1;i<=n;i++){
k=k?k-1:k; j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) ++k;
height[rk[i]]=k;
}
}
} using namespace Suffix_Array;
void calc(int x,int y,int l){
if(x+l>y) l=y-x;
ckmax(ans,(y-1)*l);
if((l<<1)>n-x+1) l=(n-x+1)/2;
ckmax(ans,l*(n-x+1-l));
}
namespace DSU{
int fa[NN],lf[NN],rt[NN];
int getf(int x){ return x==fa[x]?x:fa[x]=getf(fa[x]); }
void merge(int x,int y,int l){
if(!x||!y) return;
x=getf(x); y=getf(y);
if(x==y) return;
ckmin(lf[x],lf[y]); ckmax(rt[x],rt[y]);
calc(lf[x],rt[x],l);
fa[y]=x;
}
} using namespace DSU;
signed main(){
scanf("%s",s+1); n=strlen(s+1);
get_S(); get_H();
for(int i=1;i<=n;i++) fa[i]=lf[i]=rt[i]=id[i]=i;
sort(id+1,id+n+1,[](int a,int b){ return height[a]>height[b]; });
for(int i=1;i<=n;i++){
int x=id[i];
merge(sa[x],sa[x-1],height[x]);
}
write(ans,'\n');
return 0;
}
/*
thestormiscoming
*/
T3 高维入侵
大NB题。
求出母串的全部周期,问题转化为求 \(\sum_{i=1}^{tot}a_ix_i\) 在 \([0,m-n]\) 中能取出多少种取值,其中 \(tot\) 为周期数, \(a\) 为周期。
可以暴力跑同余最短路。关于同余最短路解决这类问题可以看 墨墨的等式 。
\(min\left\{a_i\right\}\times tot\) 是 \(O(n^2)\) 的,无法通过。我们可以用根本不会的 \(border\) 理论(之一)来优化这一过程。
一个字符串的所有周期构成 \(O(\log|s|)\) 个等差数列。
具体证明和 \(border\) 理论可以看 这篇博客。
于是把不同等差数列分开处理,将每个等差数列都描述为 \(\left\{fst,fst+dif,fst+2\times dif,\ldots,fst+len\times dif\right\}\) ,模数为 \(fst\) ,那么每个等差数列都会形成 \(gcd(dif,fst)\) 个环。不难看出每个环内 \(dis\) 最小的点都是不会被更新到的,因此断环为链进行 \(DP\) , 有
可以单调队列搞。
对于不同的等差数列进行同样的操作,但二者交接时有模数转换的问题。若原来模数为 \(lst\) ,现在模数为 \(now\) ,那么首先有 \(dis_{i} \to dis'_{i\mod now}\) 。同时由于同余意义,再做一次类似上面的转移,其中公差为 \(lst\) ,模数为 \(now\) 。不同的是现在没有 \(len\) 的限制,不用单调队列,直接断环为链后倍长进行转移即可。
\(code:\)
T3
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL; typedef unsigned long long ULL;
typedef double DB; typedef long double LDB;
#define int LL
int read(){
int x=0,f=0; char ch=getchar();
while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) x=-x, putchar('-');
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmin(int &x,int y){ x=x<y?x:y; }
void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=2000010;
int n,m,tot,now,ans,nxt[NN],brd[NN];
char s[NN];
void get_border(){
for(int j=0,i=2;i<=n;i++){
while(j&&s[j+1]!=s[i]) j=nxt[j];
if(s[j+1]==s[i]) ++j;
nxt[i]=j;
}
int u=nxt[n];
while(u){
brd[++tot]=n-u;
u=nxt[u];
}
brd[++tot]=n;
}
namespace Mod_Shortest_Path{
int l,r,top,que[NN],stk[NN<<1];
int tds[NN],dis[NN],seq[NN];
void change_mod(int mod){
int cnt=__gcd(mod,now);
for(int i=0;i<now;i++) tds[i]=dis[i];
for(int i=0;i<mod;i++) dis[i]=4e18;
for(int t,i=0;i<now;i++)
t=tds[i]%mod, ckmin(dis[t],tds[i]);
for(int t,i=0;i<cnt;i++){
stk[top=1]=i; t=(i+now)%mod;
while(t!=stk[1]) stk[++top]=t, t=(t+now)%mod;
for(int p=top,j=1;j<=p;j++) stk[++top]=stk[j];
for(int j=2;j<=top;j++)
ckmin(dis[stk[j]],dis[stk[j-1]]+now);
}
now=mod;
}
void work(int fst,int dif,int len){
if(dif<0) return;
int cnt=__gcd(fst,dif);
change_mod(fst);
for(int t,i=0;i<cnt;i++){
stk[top=1]=i; t=(i+dif)%now;
while(t!=stk[1]) stk[++top]=t, t=(t+dif)%now;
int mip=1,tmp=0;
for(int i=1;i<=top;i++)
if(dis[stk[i]]<dis[stk[mip]]) mip=i;
for(int i=mip;i<=top;i++) seq[++tmp]=stk[i];
for(int i=1;i<mip;i++) seq[++tmp]=stk[i];
que[l=r=1]=1;
for(int i=2;i<=top;i++){
while(l<=r&&que[l]<=i-len) ++l;
if(l<=r) ckmin(dis[seq[i]],dis[seq[que[l]]]+dif*(i-que[l])+fst);
while(l<=r&&dis[seq[que[r]]]-dif*que[r]>dis[seq[i]]-dif*i) --r;
que[++r]=i;
}
}
}
} using namespace Mod_Shortest_Path;
signed main(){
n=read(); m=read()-n; scanf("%s",s+1);
get_border();
memset(dis,0x3f,sizeof(dis));
now=n; dis[0]=0;
for(int i=1,j=1;i<=tot;i=j){
while(brd[j+1]-brd[j]==brd[i+1]-brd[i]) ++j;
work(brd[i],brd[i+1]-brd[i],j-i);
}
for(int i=0;i<=now;i++)
if(dis[i]<m) ans+=(m-dis[i])/now+1;
write(ans,'\n');
return 0;
}
/*
11 30
stationlost
*/

浙公网安备 33010602011771号