1. 程式人生 > >[POI2012]OKR-A Horrible Poem hash

[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