题目大意
给定一串珊瑚珠子(长度 Len≤2∗105 ),每个珠子属于种类k。现将该串珠子按照len的长度从开头依次断开,求得到最多不相同的子项链个数,并输出所有满足这个最大值的len长度。
如果分到最后,这条珠子的剩下长度不满足len,忽略最后的部分。项链是可以反转的,即(1,2,3)和(3,2,1)表示同一种样式。
Input
- 第一行一个整数n( n≤2∗105 )代表珠子的长度。
- 第二行是由空格分开的颜色 ai (1<= ai <=n)。
Output
- 第一行两个整数,第一个整数代表能获得的最大不同的子串长度,第二个整数代表能获得最大值的k的个数,第二行输出所有的k,用空格隔开。
Sample Input
21
1 1 1 2 2 2 3 3 3 1 2 3 3 1 2 2 1 3 3 2 1Sample Output
6 1
2
这个Hash初步本来是要放在 USACO 2015 February Bronze&Silver Censoring 这一道题目上的,但是考虑 坑爹的 版权问题所以只好放到这道题上了(反正都是水题)。
Hash算法:
- Hash的本质是将原来的字符串转化成用B进制表示的数。对于一段字符串,我们通过进制转化,将其变成一个B进制的数来进行维护,是远比直接字符串维护要快的。
预处理操作
同十进制一样,我们需要预处理出每一位上对应的B进制幂的值。
举个栗子,如果我们知道数字9和“在千位上添加9”的操作,我们只需要+9*Base【千位(=1000)】=9000即可完成。而将十进制改成B进制就同理了。得到B进制幂数组Base[i]。
显然这个Base[i]是会int溢出的,而一般情况下,我们使用的是Base[i]对素数取模过后的数值,这个数值在对较小数取模情况下容易产生冲突,但是用较大素数(最常用的是孪生素数1e9+7和1e9+9)冲突几率就会很小。
实时计算操作
如果我们不是直接按照对应位更新,那么字符串后面添加一个字符的操作等价于将前面的数值全部向前移动一位(有二进制<<操作符的意味),再将最后一位添加上这个数值,计算公式如下:
Hi=(Hi−1∗B+(char)c)%P判重操作
一般的情况是让我们判断目前已Hash字符串中中间某一段是否与当前询问的字符串等价。两者的区别是:
- 中间Hash的某一段相对于当前询问字符串,被移动了几个数位。
如果要成功判重,就需要将中间这段Hash重新移回到初始位置(这个操作似二进制>>操作符)。
举个栗子,如果我们要提取出 (1234567)B 中的——
- (1234)B ,按照之前的计算操作,我们可以知道H[4]就是 (1234)B 的哈希值,所以我们可以直接拿来用。
- (567)B ,已经知道H[4]等价于 (1234)B ,H[7]等价于 (1234567)B ,那么 (567)B 哈希值由下面这个公式得到:
w[i,j]=(Hj−Hi−1∗Base[j−i+1])%P此处Base的下标需要特别注意,显然 [1,i] 的这段位移长度是当前需要Hash的字符串开头到最后的长度。
Hash示例:
#include <cstdio>
#define B 233 //B 表示B进制
#define P 1000000009
//1e9+7,1e9+9是孪生素数,用这两个数可以卡除hash值重复的情况
char S[M];
int Base[M],H[M];
int Hash(int L,int R){
int num=H[R]-(1LL*H[L-1]*Base[R-L+1])%P;
if(num<0)num+=P;//可能得到的num为负值
return num;
}//123456,取出456,有123456-123*10^3=456
int main(){
scanf("%s",S+1); n=strlen(S+1);
Base[0]=1;H[0]=0;
for(int i=1;i<=n;i++)Base[i]=(1LL*Base[i-1]*B)%P;
for(int i=1;i<=n;i++)H[i]=(1LL*H[i-1]*B+S[i])%P;
int num=Hash(L,R);
return 0;
}
其实这题暴力模拟过来就好了,我们只需要通过下面这个公式来证明一下暴力操作的复杂度真的不高:
为了不拖累这么好的复杂度,我们需要在较小的复杂度内完成对当前字符串和前面所有字符串进行判重的操作。想到判重我们一般采用set O(logn) 判重。但是只要是直接对字符串进行判重而不是一个数值就一定会带上 O(n) 的复杂度。这也是Hash算法最基本的目的所在。
利用上述的Hash算法,我们就可以在 O(nlog2n) 的复杂度内完成这道题。
但实际上这题由于poi完美的多组数据,简单的Hash是无法避免冲突的。于是那么我们就需要多种降低冲突几率的方法:
- 将Hash数值的类型更改为
double
或者(unsigned) long long
。这样需要判断的数值范围会相应提高。 - 采用孪生素数一起判重的方法,此时set内的元素就是 pair(Hash1,Hash2) 了。这个方法是最保险的,极大地降低的冲突可能。
- 剩下的就是积累一些判重能力比较厉害的素数常数,譬如说本题采用B=200019居然神奇地卡了过去。
include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <set>
#define M 200005
#define B 1000037
#define P 1000000009
#define T 1000000007
#define Pair pair<int,int>
using namespace std;
int n,A[M];
int Base1[M],Hnxt1[M],Hback1[M];
int Base2[M],Hnxt2[M],Hback2[M];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&A[i]);
Base1[0]=1;for(int i=1;i<=n;i++)Base1[i]=1LL*Base1[i-1]*B%P;
Hnxt1[0]=0;for(int i=1;i<=n;i++)Hnxt1[i]=(1LL*Hnxt1[i-1]*B+A[i])%P;
Hback1[n+1]=0;for(int i=n;i>=1;i--)Hback1[i]=(1LL*Hback1[i+1]*B+A[i])%P;
Base2[0]=1;for(int i=1;i<=n;i++)Base2[i]=1LL*Base2[i-1]*B%T;
Hnxt2[0]=0;for(int i=1;i<=n;i++)Hnxt2[i]=(1LL*Hnxt2[i-1]*B+A[i])%T;
Hback2[n+1]=0;for(int i=n;i>=1;i--)Hback2[i]=(1LL*Hback2[i+1]*B+A[i])%T;
// for(int i=1;i<=n;i++)printf("%d\n",(Hnxt[i]-1LL*Hnxt[i-1]*B%P+P)%P);//
// for(int i=n;i>=1;i--)printf("%d\n",(Hback[i]-1LL*Hback[i+1]*B%P+P)%P);
int ans=0;vector<int>Ans;
for(int step=1;step<=n;step++){
if(n/step<ans)break;
set<Pair>S;int cnt=0;
int L=1,R=L+step-1;
while(R<=n){
int Hashnxt1=(Hnxt1[R]-1LL*Hnxt1[L-1]*Base1[R-L+1]%P+P)%P;
int Hashback1=(Hback1[L]-1LL*Hback1[R+1]*Base1[R-L+1]%P+P)%P;
int Hashnxt2=(Hnxt2[R]-1LL*Hnxt2[L-1]*Base2[R-L+1]%T+T)%T;
int Hashback2=(Hback2[L]-1LL*Hback2[R+1]*Base2[R-L+1]%T+T)%T;
Pair Hashnxt=make_pair(Hashnxt1,Hashnxt2);
Pair Hashback=make_pair(Hashback1,Hashback2);
if(S.find(Hashnxt)==S.end()&&S.find(Hashback)==S.end()){
cnt++;
S.insert(Hashnxt),S.insert(Hashback);
}
L+=step,R+=step;
}
if(cnt>ans){ans=cnt;Ans.clear();}
if(cnt==ans)Ans.push_back(step);
}
printf("%d %d\n",ans,Ans.size());
for(int i=0;i<Ans.size();i++)
printf("%d%c",Ans[i],(i==Ans.size()-1)?'\n':' ');
}