[POI2012]OKR-A Horrible Poem hash
題面: 洛谷
題解:
首先我們需要知道一個性質,串s的最小迴圈節 = len - next[len].其中next[len]表示串s的一個最長長度使得s[1] ~ s[next[len]] == s[len - next[len] + 1] ~ s[len](詳細定義參見KMP)
至於為什麼是成立的可以畫圖推一下,這個應該是比較常見的性質。
可能畫的有點醜。。。
圖中每個綠色方塊所代表的串都是相同的,因為第一個綠塊顯然與下面那塊相同,而根據next的定義,它也與第二行第二個綠塊相同……以此類推,可以一直遞推下去,直到推完整個陣列。
對於一個長度為len的串s而言,設它的最小迴圈節長度為l.
若$len = p_{1}^{k_{1}} \cdot p_{2}^{k_{2}} \cdot p_{3}^{k_{3}}...p_{t}^{k_{t}}$
則$len = p_{1}^{a_{1}} \cdot p_{2}^{a_{2}} \cdot p_{3}^{a_{3}}...p_{t}^{a_{t}}$其中$a_{i} \le k_{i}$.
首先一個串的最大迴圈節長度肯定= len;
而這個迴圈節之所以可以變小,是因為這個最大迴圈節是由很多個最短迴圈節組成。我們假設最短迴圈節為X,這這個串可以表示為XXXXXXX(若干個X)
假設X有b個。那麼我們可以每次對b縮減一個b的因子,最後使得b變為1,即使b不斷除一個數。
例如一個串s一開始可以被表示為XXXXXXXX(8個X),即b = 8
這個時候我們列舉到一個2,於是我們判斷原串是否可以被XXXX + XXXX湊出,如果可以,那麼b /= 2.
然後我們判斷原串是否可以被XX + XX + XX + XX湊出,如果可以,那麼b /= 2.
依次類推,直到已經沒有更小的迴圈節可以湊出原串位置。
其中2是b的某個質因子。
因為b的最大值為len(即迴圈節為1),所以我們一開始先從len開始,不斷列舉len的質因子,看能否消去這個因子,最後剩下的數就是答案。
判斷一個長度是否可以成立,可以用hash判斷圖中紅色部分是否相等
其中綠塊長度為x。
原理就是一開始解釋過的KMP求最小迴圈節。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 501000 5 #define p1 1000000007 6 #define p2 998244353 7 #define base 26 8 #define LL long long 9 10 int n, m, tot, top; 11 int hash1[AC], hash2[AC], pw1[AC], pw2[AC]; 12 int pri[AC], last[AC], q[AC]; 13 char s[AC]; 14 bool z[AC]; 15 16 inline int read() 17 { 18 int x = 0;char c = getchar(); 19 while(c > '9' || c < '0') c = getchar(); 20 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 21 return x; 22 } 23 24 void get()//尤拉篩 25 { 26 for(R i = 2; i <= n; i ++) 27 { 28 if(!z[i]) pri[++ tot] = i, last[i] = i; 29 for(R j = 1; j <= tot; j ++) 30 { 31 int now = pri[j]; 32 if(now * i > n) break; 33 z[now * i] = true, last[now * i] = now; 34 if(!(i % now)) break; 35 } 36 } 37 } 38 39 void pre() 40 { 41 n = read(); 42 scanf("%s", s + 1); 43 } 44 45 void build()//求字首hash值 46 { 47 pw1[0] = pw2[0] = 1; 48 for(R i = 1; i <= n; i ++) 49 { 50 hash1[i] = (1ll * hash1[i - 1] * base + s[i] - 'a' + 1) % p1; 51 hash2[i] = (1ll * hash2[i - 1] * base + s[i] - 'a' + 1) % p2; 52 pw1[i] = 1ll * pw1[i - 1] * base % p1;//存下base的i次方 53 pw2[i] = 1ll * pw2[i - 1] * base % p2; 54 } 55 } 56 57 LL cal(int l, int r, bool w){ 58 if(!w) return ((1ll * hash1[r] - 1ll * hash1[l - 1] * pw1[r - l + 1] % p1) + p1) % p1; 59 else return ((hash2[r] - 1ll * hash2[l - 1] * pw2[r - l + 1] % p2) + p2) % p2; 60 } 61 62 bool check(int l, int r, int x){//測試區間[l, r]的迴圈結是否可能為x 63 return (cal(l + x, r, 0) == cal(l, r - x, 0)) && (cal(l + x, r, 1) == cal(l, r - x, 1)); 64 } 65 66 void work() 67 { 68 m = read(); 69 for(R i = 1; i <= m; i ++) 70 { 71 int l = read(), r = read(), x = r - l + 1; 72 top = 0; 73 while(x != 1) q[++ top] = last[x], x /= last[x]; 74 x = r - l + 1; 75 for(R i = 1; i <= top; i ++) 76 if(check(l, r, x / q[i])) x /= q[i]; 77 printf("%d\n", x); 78 } 79 } 80 81 int main() 82 { 83 // freopen("in.in", "r", stdin); 84 pre(); 85 get();//處理last陣列 86 build();//構建hash陣列 87 work(); 88 // fclose(stdin); 89 return 0; 90 }View Code