項鍊(最小表示法)
阿新 • • 發佈:2020-07-27
題目
思路
看到這道題目我腦子裡面第一個閃過的是KMP,但是看到第二問我就發現竟然是我不會的最小表示法。
首先明確一個思路,如果對於兩個東西我們要確定是否相同,最好的方法就是確定一個最小的東西判斷相等,例如在AcWing 156. 矩陣 中就是最小的瀏覽順序,而這裡則是最小的字典序,所以我們不難想到最小表示法。
什麼?你說我總是講的不好,我也沒打算講啊,這個演算法OIwiki講的挺好的,我就只做一些註釋吧以及答疑吧。
首先,暴力是我們每次比較\(i\)和\(j\)開始的迴圈同構,把當前比較到的位置記作 ,每次遇到不一樣的字元時便把大的跳過,最後剩下的就是最優解。
int k = 0, i = 0, j = 1; while (k < n && i < n && j < n) { if (sec[(i + k) % n] == sec[(j + k) % n]) { ++k; } else { if (sec[(i + k) % n] > sec[(j + k) % n]) ++i; else ++j; k = 0; if (i == j) i++; } } i = min(i, j);
但是這個很明顯在\(aaaaa...b\)時炸了,這個演算法是\(O(n^2)\)的。
但是,我們可以優化!
int k = 0, i = 0, j = 1; while (k < n && i < n && j < n) { if (sec[(i + k) % n] == sec[(j + k) % n]) { k++; } else { sec[(i + k) % n] > sec[(j + k) % n] ? i = i + k + 1 : j = j + k + 1; if (i == j) i++; k = 0; } } i = min(i, j);
至於正確性嗎,沒優化的肯定是對的,優化並沒有改變正確性,所以也是對的,沒有毛病。
問題一
為什麼是最後是選擇\(min(i,j)\)呢?
首先看看退出的條件,那麼在\(i,j\)跳出\(n\)的範圍是,很明顯\(min\)會選另外一個,但是\(k=n\)時呢?說明兩個都是最小表示,隨便選一個都可以。
問題二
\(k=n\)是個什麼情況?
說明同時有兩個位置都是最小表示法,那麼這個字串肯定是由一個迴圈節迴圈組成的。
有人會問會不會是\(n\)個迴圈節加上半個迴圈節呢?
不會的,你畫個圖就會發現這樣子的話\(k\)是不可能等於\(n\)的。
程式碼
#include<cstdio> #include<cstring> #define N 2100000 using namespace std; char a[N],b[N]; int n; int zxbsf(char *s)//求最小表示法的位置 { int i=1,j=2,k=0; while(i<=n && j<=n && k<n) { if(s[i+k]==s[j+k])k++; else { if(s[i+k]>s[j+k])i=i+k+1; else j=j+k+1; if(i==j)j++; k=0; } } return i<=n?i:j;//其實和min大同小異。 } int main() { scanf("%s",a+1);scanf("%s",b+1);n=strlen(a+1); for(int i=2*n;i>n;i--)a[i]=a[i-n],b[i]=b[i-n];//複製一份 int x=zxbsf(a),y=zxbsf(b); int t=0; for(int i=0;i<n;i++) { if(a[x+i]!=b[y+i]) { t=1; break; } } if(t==1)printf("No\n"); else { printf("Yes\n"); for(int i=0;i<n;i++)printf("%c",a[x+i]); printf("\n"); } return 0; }