luogu P6229 [BalticOI 2019 Day2]项链
题面传送门
希望早日有好的spj。
看到这道题应该可以直接秒了\(O(n^3)\):枚举第一个串中的两个端点,然后枚举第二个串的重构循环开始的地方,两边跑lcp/lcs看能否成立。
然后你发现lcp和lcs显然可以hash+倍增\(O(n^2logn)\)跑出来,关键是判断的地方。
设\(f_{i,j}\)为第一个串的\(j\)和第二个串的\(i\)同时向左跑能跑到的长度,\(g_{i,j}\)为向右。
那么我们需要判断的是对于一个区间\([j,k]\)有没有一个\(i\)满足\(f_{i,j}+g_{i+1,k}\geq k-j+1\)
移项,变成\(f_{i,j}+j-1\geq k-g_{i+1,k}\),也就是固定\(i,j\)时你需要找到最大的\(k\)满足这个式子,那么预处理后缀min倍增即可。
时间复杂度\(O(n^2logn)\)
code:
#include<cstdio>
#include<cstring>
#define N 3000
#define mod 1000000007
#define ll long long
#define I inline
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
int n,m,k,x,y,z,lg[N+5],st[N+5],f1[N+5],f2[N+5],now,pus,ans,tot1,tot2,l,r,mid;ll f[N+5],g[N+5],ba[N+5],inv[N+5];
char a[N+5],b[N+5];
I ll mpow(ll x,int y=mod-2){ll ans=1;while(y) (y&1)&&(ans=ans*x%mod),y>>=1,x=x*x%mod;return ans;}
I ll findf(int x,int y){return (f[y]-f[x-1]+mod)*inv[x]%mod;}
I ll findg(int x,int y){return (g[y]-g[x-1]+mod)*inv[x]%mod;}
I void swap(char &x,char &y){char c=x;x=y;y=c;}
I void work(int i,int flag){
register int j,h;
for(j=1;j<=m;j++)g[j]=(g[j-1]+(b[j]-'a'+1)*ba[j])%mod;
for(j=1;j<=n;j++){
now=i-1;pus=j;l=0;r=i-1;
while(l+1<r) mid=l+r>>1,(findg(mid,i-1)==findf(j-(i-mid)+1,j)?r:l)=mid;
f1[j]=i-r;
now=i;pus=j;l=i;r=n+1;
while(l+1<r) mid=l+r>>1,(findg(i,mid)==findf(j,j+(mid-i))?l:r)=mid;
//printf("%lld %lld\n",findg(5,6),findf(4,5));
f2[j]=l-i+1;
}
for(st[n+1]=1e9,j=n;j;j--) st[j]=min(st[j+1],j-f1[j]);
// printf("%d\n",i);printf("%s\n",b+1);for(j=1;j<=n;j++) printf("%d %d\n",f1[j],f2[j]);printf("\n");
for(j=1;j<=n;j++){
now=n;
for(h=lg[n];~h;h--) if(now>=(1<<h)&&st[now-(1<<h)+1]>f2[j]+j-1) now-=(1<<h);
if(now-j+1>ans) ans=now-j+1,/*printf("%d %d %d %d %d %s\n",ans,i,f1[now],f2[j],flag,b+1),*/tot1=j-1,tot2=(flag!=1?(m-(i+f2[j])+1):(i-f1[now]-1));
}
for(j=1;j<=m/2;j++) swap(b[j],b[m-j+1]);
}
int main(){
// freopen("necklace.in","r",stdin);freopen("necklace.out","w",stdout);
register int i,j,h;scanf("%s%s",a+1,b+1);n=strlen(a+1);m=strlen(b+1);k=max(n,m);
for(i=2;i<=k;i++)lg[i]=lg[i/2]+1;ba[0]=inv[0]=1;
for(i=1;i<=k;i++) ba[i]=ba[i-1]*131%mod,inv[i]=inv[i-1]*mpow(131)%mod;
for(i=1;i<=n;i++)f[i]=(f[i-1]+(a[i]-'a'+1)*ba[i])%mod;
for(i=1;i<=m+1;i++)work(i,1),work(m-i+2,-1);
printf("%d\n%d %d",ans,tot1,tot2);
}
交上去发现和\(O(n^3)\)暴力同分。/cy
然后卡下常发现两个lcp和lcs其实可以递推做到\(O(n^2)\)大大减小了常数。这个复杂度其实loj上就可以过了。
然而这个log让人很不爽。
看到后面这个二分,如果我们能保证f2[j]+j-1
有序我们其实可以双指针维护。
又因为这个很小不超过\(n\)所以可以桶排之后维护即可。
时间复杂度\(O(n^2)\)小常数,LOJ评测记录可以发现很快。
code:
#include<cstdio>
#include<cstring>
#define N 3000
#define mod 1000000007
#define I inline
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
int n,m,k,x,y,z,lg[N+5],st[N+5],f1[N+5],f2[N+5],now,pus,ans,tot1,tot2,f[N+5];
char a[N+5],b[N+5];
I void swap(char &x,char &y){char c=x;x=y;y=c;}
I void work(int i,int flag){
register int j,l;
for(j=n;j;j--) f1[j]=(b[i-1]==a[j])?f1[j-1]+1:0;
for(j=n;j;j--){if(f2[j-1]>1){f2[j]=f2[j-1]-1;continue;}f2[j]=0;while(j+f2[j]<=n&&i+f2[j]<=m&&b[i+f2[j]]==a[j+f2[j]]) f2[j]++;}
for(st[n+1]=1e9,j=n;j;j--) st[j]=min(st[j+1],j-f1[j]);
memset(f,0x3f,sizeof(f));for(j=1;j<=n;j++) f[f2[j]+j-1]=min(j,f[f2[j]+j-1]);l=1;
for(j=1;j<=n;j++){
while(st[l]<=j) l++;
if(l-f[j]>ans) ans=l-f[j],tot1=f[j]-1,tot2=(flag!=1?(m-(i+f2[f[j]])+1):(i-f1[l-1]-1));
}
}
int main(){
freopen("necklace.in","r",stdin);freopen("necklace.out","w",stdout);
register int i,j,h;scanf("%s%s",a+1,b+1);n=strlen(a+1);m=strlen(b+1);k=max(n,m);
for(i=1;i<=m+1;i++)work(i,1);
for(i=1;i<=m/2;i++) swap(b[i],b[m-i+1]);memset(f1,0,sizeof(f1));memset(f2,0,sizeof(f2));
for(i=1;i<=m+1;i++)work(i,-1);
printf("%d\n%d %d",ans,tot1,tot2);
}