[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

 

 

 

  

转载于:https://www.cnblogs.com/ww3113306/p/10066983.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值