HihoCoder

来源:互联网 发布:全球语音翻译软件 编辑:程序博客网 时间:2024/06/08 05:11

题目链接


题意:



小Hi想知道一段旋律中出现次数至少为K次的旋律最长是多少?


  

思路:

这个题目应该是后缀数组的几个经典之一.

首先要求重叠的最长,那么我们可以知道我们对所有的后缀排个序的话,字典序越接近的他的前缀越长,这是很显然的.也就是我们的height数组.

接着我们就可以想到求n个后缀的最长公共前缀:

 ①首先将N个字符串按照字典序排列。
    ②计算height(2), height(3)... height(n),得到height序列。 
    ③最后,height序列中的最小元素的值,就是N个字符串的最长公共前缀的长度。

所以我们可以想到,我要求至少重复k次的最长公共前缀,那么我只需要找到height数组中,连续的k-1个的最小值,这代表的就是出现了k次的每个公共前缀的数量,然后维护一下这个的最大值就是最终的结果了.

PS: 我们只需要维护出现k次就好了,可以想一下题目要求至少为k次,我们按照字典序排序后,k+1个字符的最长公共前缀是小于等于k个字符的最长公共前缀的.

#include<iostream>  #include<cstdio>  #include<cstring>  #include<algorithm> #define inf 0x3f3f3f3f using namespace std;  const int maxn = 1e5+5;  int t1[maxn], t2[maxn], c[maxn]; //c 基数排序辅助数组int a[maxn];int ra[maxn], height[maxn]; //rank数组,高度数组 int sa[maxn];  //suffix array   int n;    bool cmp(int *r, int a, int b, int l)  {      return r[a]==r[b]&&r[a+l]==r[b+l];  }    void da(int str[], int sa[], int ra[], int height[], int n, int m)  {      n++;      int i, j, p, *x = t1, *y = t2;      for(i = 0; i < m; i++) c[i] = 0;      for(i = 0; i < n; i++) c[x[i]=str[i]]++;      for(i = 1; i < m; i++) c[i] += c[i-1];      for(i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;      for(j = 1; j <= n; j<<=1)      {          p = 0;          for(i = n-j; i < n; i++) y[p++] = i;          for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i]-j;          for(i = 0; i < m; i++) c[i] = 0;          for(i = 0; i < n; i++) c[x[y[i]]]++;          for(i = 1; i < m; i++) c[i] += c[i-1];          for(i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];          swap(x, y);          p = 1; x[sa[0]] = 0;          for(i = 1; i < n; i++)              x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p-1 : p++;          if(p >= n) break;          m = p;      }      int k = 0;      n--;      for(i = 0; i <= n; i++) ra[sa[i]] = i;      for(i = 0; i < n; i++)      {          if(k) k--;          j = sa[ra[i]-1];          while(str[i+k]==str[j+k]) k++;          height[ra[i]] = k;      }  }  int main()  {     // freopen("a.in","r",stdin);      int n,K;    while(cin>>n>>K)    {    for(int i=0;i<n;i++)    scanf("%d",&a[i]);    da(a,sa,ra,height,n,127);     if(K==0){printf("%d\n",n);continue;}int ans=0;for(int i=1;i<=n-K+1;i++){int mm=inf;for(int j=0;j<K-1;j++){ if(mm>height[i+j]) mm=height[i+j];}if(mm>ans)ans=mm;}      printf("%d\n",ans);}}  

这个题还有另外一种做法就是二分.类似于最小值最大化问题,所以我们可以想到二分这个旋律的长度然后带回验证.

道理是一样的,将连续的height数组划分集合,舍掉所有LCP小于mid的,然后重新划分.这样就保证了得到的每一个集合中的LCP都说>=mid,如果有一个集合中的字符串数量>=k就满足.

#include<iostream>  #include<cstdio>  #include<cstring>  #include<algorithm>  using namespace std;  const int maxn = 1e5+5;  int t1[maxn], t2[maxn], c[maxn];  int ra[maxn], height[maxn];  int sa[maxn], num[maxn];    bool cmp(int *r, int a, int b, int l)  {      return r[a]==r[b]&&r[a+l]==r[b+l];  }    void da(int str[], int sa[], int ra[], int height[], int n, int m)  {      n++;      int i, j, p, *x = t1, *y = t2;      for(i = 0; i < m; i++) c[i] = 0;      for(i = 0; i < n; i++) c[x[i]=str[i]]++;      for(i = 1; i < m; i++) c[i] += c[i-1];      for(i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;      for(j = 1; j <= n; j<<=1)      {          p = 0;          for(i = n-j; i < n; i++) y[p++] = i;          for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i]-j;          for(i = 0; i < m; i++) c[i] = 0;          for(i = 0; i < n; i++) c[x[y[i]]]++;          for(i = 1; i < m; i++) c[i] += c[i-1];          for(i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];          swap(x, y);          p = 1; x[sa[0]] = 0;          for(i = 1; i < n; i++)              x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p-1 : p++;          if(p >= n) break;          m = p;      }      int k = 0;      n--;      for(i = 0; i <= n; i++) ra[sa[i]] = i;      for(i = 0; i < n; i++)      {          if(k) k--;          j = sa[ra[i]-1];          while(str[i+k]==str[j+k]) k++;          height[ra[i]] = k;      }  }    int n, k;    bool judge(int x)  {      int tmp = 1;      for(int i = 1; i < n; )      {          int cnt = 1;          int j = i+1;          while(height[j] >= x && j <= n) j++, cnt++;          tmp = max(tmp, cnt);          i = j;      }      return tmp >= k;  }    int main(void)  {      while(cin >> n >> k)      {          for(int i = 0; i < n; i++)              scanf("%d", &num[i]);          da(num, sa, ra, height, n, 127);          int l = 0, r = n, ans = 0;          while(l <= r)          {              int mid = (l+r)/2;              if(judge(mid)) ans = mid, l = mid+1;              else r = mid-1;          }          printf("%d\n", ans);      }      return 0;  }  

最后附上一个不错的博客.

原创粉丝点击