题意:找一个出现了m次的最长子串,以及这时的最右的位置。
hash的话代码还是比较好写的,,但是时间比SA多很多。。
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
const int N = + ;
typedef long long ll;
const int X = ;
const int mod = (int)1e9 + ; char s[N];
int m,len,pw[N];
int H[N],pos; struct node
{
int id,hash;
bool operator < (const node & temp) const
{
return hash == temp.hash ? id < temp.id : hash < temp.hash;
}
}p[N]; bool solve(int L)
{
int cnt = ;
pos = -;
for(int i=;i+L-<=len;i++)
{
int id = i;
int hash = ((ll)H[i] - (ll)H[i+L]*pw[L]) % mod;
if(hash < ) hash += mod; // 注意这里!
p[i] = (node){id, hash};
}
sort(p+, p++ len - L + ); for(int i=;i<=len-L+;i++)
{
if(i == || p[i].hash != p[i-].hash) cnt = ;
if(++cnt >= m) pos = max(pos, p[i].id);
}
return pos != -;
} int main()
{
pw[] = ;
for(int i=;i<N;i++) pw[i] = 1LL*pw[i-] * X % mod;
while(scanf("%d",&m) == && m)
{
scanf("%s",s+);
len = strlen(s+);
for(int i=len;i>=;i--) H[i] = (1LL*H[i+] * X + s[i] - 'a') % mod;
int l = , r = len, ans = -;
while(l <= r)
{
int mid = l + r >> ;
if(solve(mid)) ans = mid, l = mid + ;
else r = mid - ;
}
solve(ans);
if(ans != -) printf("%d %d\n",ans, pos-);
else puts("none");
}
return ;
}
hash的写法
SA的话,写法需要细细体会一下了。。时间减少了很多。
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
const int N = + ;
typedef long long ll; /**
* sa[i]:表示排在第i位的后缀的起始下标
* rank[i]:表示后缀suffix(i)排在第几
* height[i]:sa[i-1] 与 sa[i]的LCP(最长公共前缀)值
*
* */
/*
如果整数的话模板改成int.
加一个数a[n] = 0 。 这样他的排名是第一个。
construct(a,n+1); 字符串的话。
len = strlen(str);
construct(s,strlen(s)+1);
排名第0的是个空字符串。 height[i]:sa[i-1] 与 sa[i]的LCP(最长公共前缀)值
所以height[1] = 0;
rank[len] = 0;
sa[0] = len;
*/
int sa[N],rnk[N],height[N];
void construct(const char *s,int n,int m = ) {
static int t1[N],t2[N],c[N];
int *x = t1,*y = t2;
int i,j,k,p,l;
for (i = ; i < m; ++ i) c[i] = ;
for (i = ; i < n; ++ i) c[x[i] = s[i]] ++;
for (i = ; i < m; ++ i) c[i] += c[i - ];
for (i = n - ; i >= ; -- i) sa[--c[x[i]]] = i;
for (k = ; k <= n; k <<= ) {
p = ;
for (i = n - k; i < n; ++ i) y[p++] = i;
for (i = ; i < n; ++ i) if (sa[i] >= k) y[p++] = sa[i] - k;
for (i = ; i < m; ++ i) c[i] = ;
for (i = ; i < n; ++ i) c[x[y[i]]] ++;
for (i = ; i < m; ++ i) c[i] += c[i - ];
for (i = n - ; i >= ; -- i) sa[--c[x[y[i]]]] = y[i];
std::swap(x,y);
p = ; x[sa[]] = ;
for (i = ; i < n; ++ i)
x[sa[i]] = y[sa[i - ]] == y[sa[i]]
&& y[sa[i - ] + k] == y[sa[i] + k] ? p - : p ++;
if (p >= n) break;
m = p;
}
for (i = ; i < n; ++ i) rnk[sa[i]] = i;
for (i = ,l = ; i < n; ++ i) {
if (rnk[i]) {
j = sa[rnk[i] - ];
while (s[i + l] == s[j + l]) l++;
height[rnk[i]] = l;
if (l) l--;
}
}
} int m,pos,len;
char s[N];
bool solve(int L)
{
// 用后缀数组的话,如果重复长度就是最长的长度,需要特判
if(m == && L == len) {pos = ; return ;}
pos = -;
int cnt = ;
int temp = -; // 要用一个temp来记录当前cnt>=m的最大的位置
for(int i=;i<=len;i++)
{
if(height[i] >= L) cnt++, temp = max(temp, sa[i]);
else cnt = , temp = sa[i];
if(cnt >= m) pos = max(pos, temp);
}
return pos != -;
} int main()
{
while(scanf("%d",&m) == && m)
{
scanf("%s",s);
len = strlen(s);
construct(s, len+);
int l = , r = len;
int ans = -;
while(l <= r)
{
int mid = l + r >> ;
if(solve(mid)) ans = mid, l = mid + ;
else r = mid - ;
}
solve(ans);
if(ans != -) printf("%d %d\n",r,pos);
else puts("none");
}
return ;
}
SA的写法
顺便想说一下的是,这里不知为何下标从1开始无限WA,以后用SA还是下标从0开始好了。。毕竟不懂SA的具体原理。