P2852 后缀数组 + 单调队列

题目传送门

题意:

 \dpi{150}n 个数的序列 \dpi{150}x ,问你出现次数至少为 \dpi{150} k 的子序列的最大长度。

数据范围: \dpi{150}1 \leqslant n \leqslant 2 \cdot 10^4 \;,\; 0 \leqslant k \leqslant 10^6 \;,\; 2 \leqslant k \leqslant n 。

题解:

首先贪心的思考一下,把题意中的“至少”二字去掉。

这个相当于问 \dpi{150} min\{height[j]\} \; j \in [i , i + k - 2] 的最大值。

那我们掏出后缀数组的板子,然后求出 \dpi{150} height 数组。

然后我们用单调队列维护一下区间最小值的最大值就好了。

感受:

看了之后立刻想到了和题解一样的解法。

但是说实话我第一反应不是实现它,还是hack我自己。

不自信。

代码:

#include<bits/stdc++.h>
using namespace std ;
typedef long long ll ;
const int maxn = 1e6 + 5 ;
int rk[maxn << 1] , sa[maxn << 1] , height[maxn << 1] ;
int tmp[maxn << 1] , cnt[maxn] ;
int s[maxn] ;
int q[maxn] ;
void suffixarray(int n , int m)
{
   n ++ ;
   for(int i = 0 ; i < n * 2 + 5 ; i ++)
     rk[i] = sa[i] = height[i] = tmp[i] = 0 ;//开2 倍空间
   for(int i = 0 ; i < m ; i ++)  cnt[i] = 0 ;
   for(int i = 0 ; i < n ; i ++)  cnt[rk[i] = s[i]] ++ ;
   for(int i = 1 ; i < m ; i ++)  cnt[i] += cnt[i - 1] ;
   for(int i = 0 ; i < n ; i ++)  sa[-- cnt[rk[i]]] = i ;
   for(int k = 1 ; k <= n ; k <<= 1)
   {
     int j = 0 ;
     for(int i = 0 ; i < n ; i ++)
     {
       j = sa[i] - k ;
       if(j < 0)  j += n ;
       tmp[cnt[rk[j]] ++] = j ;
     }
     sa[tmp[cnt[0] = 0]] = j = 0 ;
     for(int i = 1 ; i < n ; i ++)
     {
       if(rk[tmp[i]] != rk[tmp[i - 1]]
       || rk[tmp[i] + k] != rk[tmp[i - 1] + k])
         cnt[++ j] = i ;
       sa[tmp[i]] = j ;
     }
     memcpy(rk , sa , n * sizeof(int)) ;
     memcpy(sa , tmp , n * sizeof(int)) ;
     if(j >= n - 1)  break ;
   }
   height[0] = 0 ;
   for(int i = 0 , k = 0 , j = rk[0] ; i < n - 1 ; i ++ , k ++)
     while(~k && s[i] != s[sa[j - 1] + k])
       height[j] = k -- , j = rk[sa[j] + 1] ;
}
int main()
{
   int n , k ;
   int num = 1e6 ;
   int ans = 0 , l = 1 , r = 0 ;
   scanf("%d%d" , &n , &k) ;
   for(int i = 0 ; i < n ; i ++)  scanf("%d" , &s[i]) ;
   if(n < k){printf("0\n") ; return 0 ;}
   suffixarray(n , num) ;
   for(int i = 2 ; i <= k ; i ++)
   {
   	  while(l <= r && height[q[r]] > height[i])  r -- ;
   	  q[++ r] = i ;
   }
   ans = max(ans , height[q[l]]) ;
   for(int i = k + 1 ; i <= n ; i ++)
   {
   	  while(l <= r && q[l] <= i - (k - 1))  l ++ ;
   	  while(l <= r && height[q[r]] > height[i])  r -- ;
   	  q[++ r] = i ;
   	  ans = max(ans , height[q[l]]) ;
   }
   printf("%d\n" , ans) ;
   return 0 ;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值