P2178 後綴數組 + 並查集

題目傳送門

題意:

調酒師 Rainbow 調製了 n 杯雞尾酒。這 n 杯雞尾酒排成一行,其中第 i 杯酒被貼上了一個標籤 s_i ,每杯酒有一個美味值a_i,每個標籤都是 26 個小寫英文字母之一,把這些酒想象爲一個字符串 s 。兩個長度都是 r 的子序列,子序列起始位置分別是 i,j(i \neq j)。如果這兩個子序列相等,那就認爲是“r 相似”。這兩個子序列的美味值是 a[i] * a[j] 。

問你“r 相似”的種類數和最大美味值。r \in[0,n-1] 。

題解:

首先衆所周知:height[i] = lcp(sa[i - 1] , sa[i])

需要把 height[i] 想象成 連接 sa[i-1] 和 sa[i] 的邊。這個很重要,想到這裏就會了。

掏出後綴數組板子,我們就有了 height 數組。建議使用結構體,存儲權值和連接的點。

然後我們按權值從大到小排列 height 數組。

逐漸加邊,然後維護所需信息就好了。

說一下怎麼維護信息:

第一個答案是多少種方法可以選出2杯“ r 相似”的酒。這道題比較特殊,因爲原圖是一條鏈,也就是一棵樹,所以每次加邊後就是把兩個連通塊連接成一個連通塊,這樣通過乘法原理,加邊時加上兩個連通塊大小的乘積就好了。

第二個答案是選擇 2 杯“r相似”的酒調兌可以得到的美味度的最大值。假如每杯酒的權值都是非負的,那麼只需要兩個連通塊合併後的最大值乘次大值就好了。現在有負權值,我們還需要考慮最小值和次小值,因爲負負得正。

每次加邊時,只對當前“r相似”維護信息即可,不用考慮“[0,r-1]相似”,最後掃一遍考慮後綴就好了。

感受:

昨天一直硬懟主席樹套線段樹,寫了200多行,自己感覺寫不出來,放棄了。

看了正解,好簡單。

我真菜。

這個思路很巧妙,而且感覺很自然,很好寫。

代碼:

#include<bits/stdc++.h>
using namespace std ;
typedef long long ll ;
const int maxn = 3e5 + 5 ;
int rk[maxn << 1] , sa[maxn << 1] , height[maxn << 1] ;
int tmp[maxn << 1] , cnt[maxn] ;
int n , a[maxn] ; 
char s[maxn] ;
ll ans1[maxn] , ans2[maxn] ;
int pre[maxn] , f[maxn][4] ;
int siz[maxn] ;
struct node
{
	int x , id ;
	bool operator < (const node &s) const
	{
		if(x != s.x)  return x > s.x ;
		else  return id < s.id ;
	}
} h[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 find(int u) 
{
   if(pre[u] == u)  return u ;
   return pre[u] = find(pre[u]) ;
}
void join(int k , int x , int y)
{
   int fx = find(x) ;
   int fy = find(y) ;
   pre[fx] = fy ;
   ans1[k] += ll(siz[fx]) * siz[fy] ;
   if(f[fx][0] > f[fy][0])
     f[fy][1] = f[fy][0] , f[fy][0] = f[fx][0] ;
   else
     f[fy][1] = max(f[fy][1] , f[fx][0]) ;
   if(f[fx][3] < f[fy][3])
     f[fy][2] = f[fy][3] , f[fy][3] = f[fx][3] ;
   else
     f[fy][2] = min(f[fy][2] , f[fx][3]) ;
   ll c = max(ll(f[fy][0]) * f[fy][1] , ll(f[fy][2]) * f[fy][3]) ;
   ans2[k] = max(ans2[k] , c) ;
   siz[fy] += siz[fx] ;
}
int main()
{
   scanf("%d" , &n) ;
   scanf("%s" , s) ;
   for(int i = 0 ; i < n ; i ++)  scanf("%d" , &a[i]) ;
   suffixarray(n , 200) ;
   for(int i = 2 ; i <= n ; i ++)
     h[i].x = height[i] , h[i].id = i ;
   sort(h + 2 , h + n + 1) ;
   for(int i = 0 ; i < n ; i ++)  
   {
   	  pre[i] = i ;
   	  siz[i] = 1 ; 
   	  f[i][0] = a[i] ;
   	  f[i][1] = -2e9 ;
   	  f[i][2] = 2e9 ;
   	  f[i][3] = a[i] ;
   }  
   for(int i = 0 ; i <= n ; i ++)
      ans2[i] = -1e18 ;
   for(int i = 2 ; i <= n ; i ++)
      join(h[i].x , sa[h[i].id - 1] , sa[h[i].id]) ;
   for(int i = n - 1 ; i >= 0 ; i --)
   {
   	  ans1[i] += ans1[i + 1] ;
      ans2[i] = max(ans2[i] , ans2[i + 1]) ; 
   }
   for(int i = 0 ; i <= n - 1 ; i ++)
   {
   	  if(ans1[i] == 0)  printf("0 0\n") ;
   	  else
   	    printf("%lld %lld\n" , ans1[i] , ans2[i]) ;    
   }
   return 0 ;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章