P2870 后缀数组

题目传送门

题意:

字符串 t 初始是空串。

给你一个字符串 \dpi{150}s ,可以选择从串首或串尾拿走一个字符,把这个字符拼接到 t 的尾部。

输出最小字典序的 t 。

数据范围:字符串长度不超过 10^6 。

题解:

当前串首和串尾的字符不同,那么选择字典序小的那个字符就好了。

如果串首和串尾的字符相同,设串首字符是 s[l] ,串尾字符是 s[r] 。

我们需要比较内层字符的大小,即比较 s[l+1] 和 s[r-1] 的大小。

如果还相同,那就继续向内层比较,直到找到不同的字符,如果一直相同,那选串首和选串尾是一样的。

暴力做是 O(n^2) 。因为每次选字符时要向内层比较的次数是 O(n) 。选 n 次那就是 O(n^2) 。

考虑后缀数组优化。

我们把 s 的正序和 s 的倒序拼接成字符串 h

举例子:s = abca ,那么 h=abcaacba 。

模拟一遍就知道是怎么优化的了。

字符串下标从 0 开始,初始时 l=0,r=3 。

现在 s[l]=s[r] ,然后我们比较 s[l+1] 和 s[r-1] 的大小关系。

其实我们就是在比较 abcaabca 和 acba 的大小关系。这个后缀数组预处理后,就可以 O(1) 比较了。

时间复杂度:O(nlogn + n)

感受:

想不到用后缀数组。

并且题面的数据范围是错的。我开50W一直RE。

代码:

#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] ;
char c[15] , s[maxn << 1] ;
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 len ;
   scanf("%d" , &len) ;
   getchar() ;
   for(int i = 0 ; i < len ; i ++)
   {
   	  s[i] = getchar() ;
	  getchar() ; 
   }
   for(int i = 0 ; i < len ; i ++)
     s[2 * len - i - 1] = s[i] ;
   suffixarray(2 * len , 200) ;
   int l = 0 , r = len - 1 ;
   int cnt = 0 ;
   while(l <= r)
   {
   	  if(s[l] < s[r])  printf("%c" , s[l]) , l ++ ;
	  else if(s[r] < s[l])  printf("%c" , s[r]) , r -- ;
	  else
	  {
	  	 if(rk[l] < rk[2 * len - 1 - r])  printf("%c" , s[l]) , l ++ ;
	  	 else  printf("%c" , s[r]) , r -- ;
	  }
	  cnt ++ ;
   	  if(cnt % 80 == 0)  printf("\n") ;
   }
   printf("\n") ;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值