洛谷題目頁面傳送門 & CodeForces題目頁面傳送門
給定$2$個字符串$a,b,|a|=n,|b|=m$,求最長的既在$a$中出現剛好$1$次又在$b$中出現剛好$1$次的非空字符串的長度,若是不存在輸出$-1$。html
$n,m\in[1,5000]$。c++
emmm,數據範圍很不友好,$\mathrm O(nm)$帶$\log$都不行。。。算法
考慮枚舉$a$的子串。枚舉子串能夠轉化爲枚舉全部後綴的全部前綴,這樣一來就有了「前綴」這個東西能夠利用。數組
咱們在枚舉$a$的後綴$a_{i\sim n}$的時候,令$c=a_{i\sim n}+\texttt{!}+a+\texttt{@}+b,s=|c|$。對$c$跑一遍Z算法(若是聰明的讀者還不知道Z算法是什麼,please點擊這個),就能夠知道後綴$a_{i\sim n}$在$a,b$中的出現狀況了。spa
咱們先把$z_{c,n-i+3\sim 2n-i+2},z_{c,2n-i+4\sim s}$分別裝在$2$個桶$buc1,buc2$裏,即$buc1_j$表示使得從$a$的這個位置日後和$a_{i\sim n}$的前綴匹配最長長度爲$j$的位置數,$buc2$相似。但是咱們想要的是使得從$a$的這個位置日後和$a_{i\sim n}$的前綴匹配最長長度${\ge j}$的位置數,也就是使得從$a$的這個位置日後和$a_{i\sim n}$的前綴可以匹配$j$這麼長的位置數。因而咱們能夠從$j=n-i+1$到$j=1$從大到小枚舉即將被check的$a_{i\sim n}$的前綴的長度$j$,每次若$buc1_j=buc2_j=1$,則check成功,更新答案$ans=\max(ans,j)$,而後令$buc1_{j-1}=buc1_{j}+buc1_{j-1},buc2_{j-1}=buc2_{j}+buc2_{j-1}$便可。考慮爲何這麼從大到小遞推是對的:首先$buc1_{n-i+1},buc2_{n-i+1}$原本就有咱們想要的意思。每次更新$buc1_{j-1},buc2_{j-1}$都會把它們變成咱們想要的意思下的值~~(感性理解理解)~~,因而每到一個$j$,$buc1_j,buc2_j$都會是咱們想要的意思咯。(想想就會發現,上述那個遞推就是$[1,buc1_j/buc2_j]$區間$+1$的差分。固然若是想不到差分的話,線段樹或樹狀數組是比較容易想的,可是帶$\log$,過不掉。。。)code
這樣複雜度就是$\mathrm O(n(n+m))$,僥倖過。htm
下面考慮哈希怎麼作。很顯然是作不了的。。。最快也就是按上述方法,用哈希+二分求$z$數組,但數據範圍不友好啊QWQblog
對了,數據不清空,爆零兩行淚。每枚舉一個$a$的後綴時,都要清空$2$個桶!!!ci
下面上代碼:字符串
#include<bits/stdc++.h> using namespace std; const int inf=0x3f3f3f3f; const int N=5000,M=5000; int n,m,s;//|a|,|b|,|c| char a[N+5],b[M+5],c[2*N+M+5]/*a的後綴+'!'+a+'@'+b*/; int z[2*N+M+1];//z數組 void z_init(){//Z算法 int zl=0,zr=0; for(int i=2;i<=s;i++) if(zr<i){ z[i]=0; while(i+z[i]<=s&&c[i+z[i]]==c[1+z[i]])z[i]++; if(z[i])zl=i,zr=i+z[i]-1; } else if(i+z[i-zl+1]<=zr)z[i]=z[i-zl+1]; else{ z[i]=zr-i+1; while(i+z[i]<=s&&c[i+z[i]]==c[1+z[i]])z[i]++; zl=i;zr=i+z[i]-1; } // cout<<"z";for(int i=2;i<=s;i++)cout<<z[i];puts(""); } int buc1[N+1],buc2[N+1];//2個桶 int main(){ cin>>a+1>>b+1; n=strlen(a+1);m=strlen(b+1); int ans=inf; for(int i=1;i<=n;i++){//枚舉後綴的左端點 s=0; for(int j=i;j<=n;j++)c[++s]=a[j]; c[++s]='!'; for(int j=1;j<=n;j++)c[++s]=a[j]; c[++s]='@'; for(int j=1;j<=m;j++)c[++s]=b[j]; c[s+1]=0; //上面都在造c // cout<<c+1<<"\n"; z_init(); memset(buc1,0,sizeof(buc1));memset(buc2,0,sizeof(buc2));//數據不清空,爆零兩行淚 // for(int j=n-i+3;j<=2*n-i+2;j++)cout<<c[j];cout<<" ";for(int j=2*n-i+4;j<=s;j++)cout<<c[j];puts(""); for(int j=n-i+3;j<=2*n-i+2;j++)buc1[z[j]]++;//裝到桶裏面 for(int j=2*n-i+4;j<=s;j++)buc2[z[j]]++;//同上 for(int j=n-i+1;j;j--){//枚舉後綴的前綴的長度 // printf("buc1[%d]=%d buc2[%d]=%d\n",j,buc1[j],j,buc2[j]); if(buc1[j]==1&&buc2[j]==1)ans=min(ans,j);//若是各出現剛好1次,則更新答案 buc1[j-1]+=buc1[j];buc2[j-1]+=buc2[j];//將buc1[j-1],buc2[j-1]變爲咱們想要的意思 } // puts(""); // cout<<"ans="<<ans<<"\n"; } printf("%d",ans<inf?ans:-1); return 0; }
原文出處:https://www.cnblogs.com/ycx-akioi/p/CodeForces-427D.html