BZOJ_2882_工藝
給出一個字符串,求與它循環同構的串中字典序最小的串。
后綴數組/后綴自動機+map 都可以在O(nlogn)的時間復雜度求出。
實際上有一個專門解決這類問題的算法:最小表示法。
首先把串復制一遍貼在原串后面,這樣每個循環同構的串可以用S[i]~S[i+n-1]來表示,設為w(i)。
也就是說我們把所有的串拿出來了,比較就行了。
在比較w(i)和w(j)時的最壞時間復雜度是O(n)的,也就是說這只是一個暴力的做法。
實際上我們不需要對所有的w(i)都進行一次比較。
假設比較w(i)和w(j)時比較了k個字符,直到k+1個字符不同。
那么我們將字典序大的那邊指針向后跳k+1即可,因為已經知道有比這些串小的串了(就在另一個指針的后面)
相當於每個指針最多向后跳n次,復雜度就變成O(n)的了,非常好寫。
代碼:
while(i<=n&&j<=n) { for(k=0;k<n&&w[i+k]==w[j+k];k++); if(k==n) break; if(w[i+k]>w[j+k]) { i+=k+1; if(i==j) i++; }else { j+=k+1; if(i==j) j++; } } i=min(i,j);
BZOJ_2882代碼:
#include <cstdio> #include <string.h> #include <algorithm> using namespace std; inline char nc() { static char buf[100000],*p1,*p2; return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; } inline int rd() { int x=0; char s=nc(); while(s<'0'||s>'9') s=nc(); while(s>='0'&&s<='9') x=(x<<3)+(x<<1)+s-'0',s=nc(); return x; } char pbuf[10000000] , *pp = pbuf; inline void write(int x) { static int sta[35]; int top = 0; if(!x)sta[++top]=0; while(x) sta[++top] = x % 10 , x /= 10; while(top) *pp ++ = sta[top -- ] ^ '0'; } #define N 600050 int w[N],n; int main() { n=rd(); register int i; for(i=1;i<=n;i++) w[i]=rd(),w[i+n]=w[i]; register int j=2,k; i=1; while(i<=n&&j<=n) { for(k=0;k<n&&w[i+k]==w[j+k];k++); if(k==n) break; if(w[i+k]>w[j+k]) { i+=k+1; if(i==j) i++; }else { j+=k+1; if(i==j) j++; } } i=min(i,j);j=i+n-1; while(i<=j) write(w[i]),*pp++=' ',i++; fwrite(pbuf,1,pp-pbuf,stdout); return 0; }
BZOJ_1398_Vijos1382尋找主人 Necklace
題意:判斷兩個串是否循環同構。
分析:分別求兩個串的最小表示,然后比較即可,時間復雜度O(n)。
代碼:
#include <cstdio> #include <string.h> #include <algorithm> using namespace std; #define N 2000050 int n; int a[1000050]; char w[N]; int main() { int i; scanf("%s",w+1); int n=strlen(w+1); for(i=1;i<=n;i++) w[i+n]=w[i]; int j=2,k; i=1; while(i<=n&&j<=n) { for(k=0;k<n&&w[i+k]==w[j+k];k++) ; if(k==n) break; if(w[i+k]>w[j+k]) { i+=k+1; if(i==j) i++; }else { j+=k+1; if(i==j) j++; } } i=min(i,j); for(k=1;k<=n;k++) a[k]=w[i+k-1]; scanf("%s",w+1); if(strlen(w+1)!=n) { puts("No"); return 0; } for(i=1;i<=n;i++) w[i+n]=w[i]; j=2;i=1; while(i<=n&&j<=n) { for(k=0;k<n&&w[i+k]==w[j+k];k++) ; if(k==n) break; if(w[i+k]>w[j+k]) { i+=k+1; if(i==j) i++; }else { j+=k+1; if(i==j) j++; } } i=min(i,j); for(k=1;k<=n;k++) if(a[k]!=w[i+k-1]) { puts("No"); return 0; } puts("Yes"); for(i=1;i<=n;i++) printf("%c",a[i]); }
總結(來自周源《淺析“最小表示法”思想在字符串循環同構問題中的應用》,我認為說得很好):
“最小表示法”是判斷兩種事物本質是否相同的一種常見思想,它的通用性也是被人們認可的——無論是搜索中判重技術,還是判斷圖的同構之類復雜的問題,它都有着無可替代的作用。仔細分析可以得出,其思想精華在於引入了“序”這個概念,從而將紛繁的待處理對象化為單一的形式,便於比較。