P4248 后缀数组 + 单调队列

题目传送门

题意:

给定一个长度为 \dpi{150} n 的字符串 $S$,令 \dpi{150}T_i 表示它从第 \dpi{150}i 个字符开始的后缀。

计算 \sum_{1 \leq i < j \leq n}\left ( len(T_i) + len(T_j) - 2 * lcp(T_i,T_j) \right ) 。

数据范围: 2 \leq n \leq 5 \cdot 10^5 。

题解:

首先我们掏出后缀数组板子,获得 height 数组。

我们所需要求的其实是 \sum_{1 \leq i < j \leq n} lcp(T_i,T_j) ,其他的都是常数。

我们需要求的就是 height 数组的所有区间最小值的和。如何不明白,就仔细想一想。

我因为不会求所有区间最小值的和,被办了一下午和一晚上,一直以为我的单调栈是没有问题的。

假如我现在有3个数1,2,1,我的[1,3]区间会被统计两次,也就是由于重复元素作为最小值的存在,我计算多了。

所以我们需要单调队列维护半闭半开区间。

具体操作:维护单调递增栈。 height[L[i] - 1] 是 height[i] 左边第一个小于等于height[i] 的数,height[R[i] + 1] 是 height[i] 右边第一个小于 height[i] 的数。认为 height[i] 是[i - L[i] , i + R[i]] 的最小值。

代码:

#include<bits/stdc++.h>
using namespace std ;
typedef long long ll ;
const int maxn = 5e5 + 5 ;
int rk[maxn << 1] , sa[maxn << 1] , height[maxn << 1] ;
int tmp[maxn << 1] , cnt[maxn] ;
char s[maxn] ;
/*struct node
{
	int id , x ;
} q[maxn] ;*/
int q[maxn] ;
int L[maxn] , R[maxn] ;
int ans[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] ;
}
void solve(int n)
{
	ll num = (ll)(n - 1) * n * (n + 1) / 2 ;
	n ++ ;
	height[n] = 0 ;
    /*int l = 1 , r = 0 ;
    q[++ r].id = 1 , q[r].x = 1 ;
	for(int i = 2 ; i <= n ; i ++)
	{
		int z = 0 ;
		int c = 0 ;
		while(l <= r && height[q[r].id] > height[i])
		{
			ans[q[r].id] += q[r].x ;
			ans[q[r].id] += z + c ;
			z ++ ;
			c += q[r].x ;
			r -- ;
		}
		q[++ r].id = i ;
		q[r].x = z + c ;
	}	
	for(int i = 1 ; i <= n ; i ++)
      cout << i << ' ' << height[i] << ' ' << ans[i] + 1 << '\n' ;*/
    int l = 1 , r = 0 ;
	for(int i = 2 ; i <= n ; i ++)
	{
		while(l <= r && height[q[r]] > height[i])
		{
			L[i] += L[q[r]] + 1 ;
			r -- ;
		}
		q[++ r] = i ;
	}
	l = 1 , r = 0 ;
	for(int i = n ; i >= 2 ; i --)
	{
		while(l <= r && height[q[r]] >= height[i])
		{
			R[i] += R[q[r]] + 1 ;
			r -- ;
		}
		q[++ r] = i ;
	}
	//for(int i = 1 ; i <= n ; i ++)
	  //ans[i] = L[i] + R[i] + 1 , cout << i << ' ' << height[i] << ' ' << ans[i] << '\n' ;
	for(int i = 2 ; i <= n ; i ++)
	  num -= 2ll * height[i] * (R[i] + 1) * (L[i] + 1) ;
	printf("%lld\n" , num) ;	
}
int main()
{
   int len ;
   //freopen("data.in","r",stdin);                  //从文件data.in中读入数据
   //freopen("B.out","w",stdout);    //输出的结果存在ZhengJie.out文件中	
   scanf("%s" , s) ;
   len = strlen(s) ;
   suffixarray(len , 200) ;
   solve(len) ;
   //printf("%s" , s) ;
   return 0 ;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值