[HAOI2016]找相同字符
题目描述
给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两个子串中有一个位置不同。
两行,两个字符串s1,s2,长度分别为n1,n2。1 <=n1, n2<= 200000,字符串中只有小写字母
题解:
s1'#'s2拼起来。
求height
要求∑(i)∑(j)LCP(rk[i],rk[j]) 1<=i<=l1, l1+2<=j<=l1+l2+1
考虑找排名序列。
其实是对属于s1的i之前的,属于s2的j,min(hei[k]),j+1<=k<=i
对一些前面的某些位置取min再做和
可以分治。
对于跨中点的区间,钦定最小值在左边,做一遍
钦定最小值在右边,做一遍
对于两边最小值相同的,一个取等一个不取等
height[i]=lcp(suff[sa[i],suff[sa[i-1]])
注意边界处理
#include<bits/stdc++.h> #define reg register int #define il inline #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=200000+5; const int inf=0x3f3f3f3f; int n,m; int l1,l2; char s1[N],s2[N],s[2*N]; int x[4*N],y[4*N],rk[2*N],sa[2*N]; int c[2333],num; int hei[2*N]; void getsa(){ m=233; for(reg i=1;i<=n;++i) ++c[x[i]=s[i]]; //for(reg i=1;i<=n;++i) cout<<x[i]<<" ";cout<<endl; for(reg i=2;i<=m;++i) c[i]+=c[i-1]; for(reg i=1;i<=n;++i) sa[c[x[i]]--]=i; //for(reg i=1;i<=n;++i) cout<<sa[i]<<" ";cout<<endl; for(reg k=1;k<=n;k<<=1){ num=0; for(reg i=n-k+1;i<=n;++i) y[++num]=i; for(reg i=1;i<=n;++i) if(sa[i]>k) y[++num]=sa[i]-k; for(reg i=1;i<=m;++i) c[i]=0; for(reg i=1;i<=n;++i) ++c[x[i]]; for(reg i=2;i<=m;++i) c[i]+=c[i-1]; for(reg i=n;i>=1;--i) sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); num=1;x[sa[1]]=1; for(reg i=2;i<=n;++i){ x[sa[i]]=(y[sa[i]]==y[sa[i-1]])&&(y[sa[i]+k]==y[sa[i-1]+k])?num:++num; } if(num==n) break; m=num; } } void gethei(){ int k=0; for(reg i=1;i<=n;++i) rk[sa[i]]=i; for(reg i=1;i<=n;++i){ if(rk[i]==1) continue; if(k) --k; int j=sa[rk[i]-1]; while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) ++k; hei[rk[i]]=k; } } ll ans; int be[2*N]; void divi(int l,int r){ // cout<<" divi "<<l<<" and "<<r<<"-------------------------------"<<endl; if(l==r) return ; int mid=(l+r)>>1; // cout<<" mid "<<mid<<endl; int cnt1=0,cnt2=0; int mir=inf,mil=inf; int ptr=mid+1; for(reg i=mid+1;i<=r;++i){ mir=min(mir,hei[i]); while(ptr-1>=l&&hei[ptr]>=mir) { if(be[ptr-1]==1) ++cnt1; else if(be[ptr-1]==2) ++cnt2; ptr--; } if(be[i]==1) ans+=(ll)cnt2*mir; else if(be[i]==2) ans+=(ll)cnt1*mir; } cnt1=0,cnt2=0; ptr=mid+1; for(reg i=mid;i>=l;--i){ mil=min(mil,hei[i+1]); while(ptr<=r&&hei[ptr]>mil){ if(be[ptr]==1) ++cnt1; else if(be[ptr]==2) ++cnt2; ptr++; } if(be[i]==1) ans+=(ll)cnt2*mil; else if(be[i]==2) ans+=(ll)cnt1*mil; } divi(l,mid); divi(mid+1,r); } int main(){ scanf("%s%s",s1+1,s2+1); l1=strlen(s1+1);l2=strlen(s2+1); n=l1+l2+1; for(reg i=1;i<=l1;++i) s[i]=s1[i]; s[l1+1]='#'; for(reg i=1;i<=l2;++i) s[l1+1+i]=s2[i]; // cout<<n<<" : "<<s+1<<endl; getsa();gethei(); for(reg i=1;i<=l1;++i) be[rk[i]]=1; be[rk[l1+1]]=3; for(reg i=1;i<=l2;++i) be[rk[l1+1+i]]=2; // for(reg i=1;i<=n;++i){ // cout<<hei[i]<<" "; // }cout<<endl; // for(reg i=1;i<=n;++i){ // cout<<be[i]<<" "; // }cout<<endl; // divi(1,n); printf("%lld",ans); return 0; } } int main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/11/15 10:51:22 */
更优秀地,可以用单调栈做;
从顶到底,高度单调递减,高度即位置与当前i之间的height取min
维护这个矩形集合的面积。
遇到hei[i]<hei[top]的情况,直接砍下去,然后必要的时候,和sta[top-1]合并
stack中每个元素维护w和h,表示矩形宽和高。
全局数组now维护面积
s1,s2在前分别做一遍
#include<bits/stdc++.h> #define reg register int #define il inline #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=200000+5; const int inf=0x3f3f3f3f; int n,m; int l1,l2; char s1[N],s2[N],s[2*N]; int x[4*N],y[4*N],rk[2*N],sa[2*N]; int c[2333],num; int hei[2*N]; struct po{ int w,h; po(){}; po(int a,int b){ w=a,h=b; } }sta[2*N]; int top; void getsa(){ m=233; for(reg i=1;i<=n;++i) ++c[x[i]=s[i]]; //for(reg i=1;i<=n;++i) cout<<x[i]<<" ";cout<<endl; for(reg i=2;i<=m;++i) c[i]+=c[i-1]; for(reg i=1;i<=n;++i) sa[c[x[i]]--]=i; //for(reg i=1;i<=n;++i) cout<<sa[i]<<" ";cout<<endl; for(reg k=1;k<=n;k<<=1){ num=0; for(reg i=n-k+1;i<=n;++i) y[++num]=i; for(reg i=1;i<=n;++i) if(sa[i]>k) y[++num]=sa[i]-k; for(reg i=1;i<=m;++i) c[i]=0; for(reg i=1;i<=n;++i) ++c[x[i]]; for(reg i=2;i<=m;++i) c[i]+=c[i-1]; for(reg i=n;i>=1;--i) sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); num=1;x[sa[1]]=1; for(reg i=2;i<=n;++i){ x[sa[i]]=(y[sa[i]]==y[sa[i-1]])&&(y[sa[i]+k]==y[sa[i-1]+k])?num:++num; } if(num==n) break; m=num; } } void gethei(){ int k=0; for(reg i=1;i<=n;++i) rk[sa[i]]=i; for(reg i=1;i<=n;++i){ if(rk[i]==1) continue; if(k) --k; int j=sa[rk[i]-1]; while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) ++k; hei[rk[i]]=k; } } ll ans; ll now; int be[2*N]; int sum1[2*N],sum2[2*N]; int main(){ scanf("%s%s",s1+1,s2+1); l1=strlen(s1+1);l2=strlen(s2+1); n=l1+l2+1; for(reg i=1;i<=l1;++i) s[i]=s1[i]; s[l1+1]='#'; for(reg i=1;i<=l2;++i) s[l1+1+i]=s2[i]; // cout<<n<<" : "<<s+1<<endl; getsa();gethei(); for(reg i=1;i<=l1;++i) be[rk[i]]=1; be[rk[l1+1]]=3; for(reg i=1;i<=l2;++i) be[rk[l1+1+i]]=2; for(reg i=2;i<=n;++i){ //cout<<" iiiiii "<<i<<" ------------------------ "<<endl; while(top&&hei[i]<sta[top].h){ if(top>1){ if(sta[top-1].h>hei[i]){ now-=(sta[top].h-sta[top-1].h)*sta[top].w; sta[top-1].w+=sta[top].w; --top; } else{ now-=(sta[top].h-hei[i])*sta[top].w; sta[top].h=hei[i]; } } else{ now-=(sta[top].h-hei[i])*sta[top].w; sta[top].h=hei[i]; } } sta[++top]=po(be[i-1]==1,hei[i]); now+=(be[i-1]==1)*hei[i]; if(be[i]==2) ans+=now; //cout<<" ans "<<ans<<" now "<<now<<endl; } top=0; now=0; for(reg i=2;i<=n;++i){ while(top&&hei[i]<sta[top].h){ if(top>1){ if(sta[top-1].h>hei[i]){ now-=(sta[top].h-sta[top-1].h)*sta[top].w; sta[top-1].w+=sta[top].w; --top; } else{ now-=(sta[top].h-hei[i])*sta[top].w; sta[top].h=hei[i]; } } else{ now-=(sta[top].h-hei[i])*sta[top].w; sta[top].h=hei[i]; } } sta[++top]=po(be[i-1]==2,hei[i]); now+=(be[i-1]==2)*hei[i]; if(be[i]==1) ans+=now; } printf("%lld",ans); return 0; } } int main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/11/15 10:51:22 */