【bzoj3879】SvT 后缀数组+倍增RMQ+单调栈
題目描述
(我并不想告訴你題目名字是什么鬼)
有一個長度為n的僅包含小寫字母的字符串S,下標范圍為[1,n].
現在有若干組詢問,對于每一個詢問,我們給出若干個后綴(以其在S中出現的起始位置來表示),求這些后綴兩兩之間的LCP(LongestCommonPrefix)的長度之和.一對后綴之間的LCP長度僅統計一遍.
輸入
第一行兩個正整數n,m,分別表示S的長度以及詢問的次數.
接下來一行有一個字符串S.
接下來有m組詢問,對于每一組詢問,均按照以下格式在一行內給出:
首先是一個整數t,表示共有多少個后綴.接下來t個整數分別表示t個后綴在字符串S中的出現位置.
輸出
對于每一組詢問,輸出一行一個整數,表示該組詢問的答案.由于答案可能很大,僅需要輸出這個答案對于23333333333333333(一個巨大的質數)取模的余數.樣例輸入
7 3
popoqqq
1 4
2 3 5
4 1 2 5 6
樣例輸出
0
0
2
題解
后綴數組+倍增RMQ+單調棧
首先預處理出sa和height數組。
然后對于每組詢問,將要求的后綴去重后按照rank從小到大排序。
由于我們有:LCP(a,c)=min(LCP(a,b),LCP(b,c)),其中rank[a]<rank[b]<rank[c]
所以我們只需要知道相鄰兩個要求的后綴之間的LCP,即可推出任意兩個后綴的LCP。
這里求LCP的方式是倍增RMQ,所以我偷改了height的定義:height[i][j]表示排名為i-2^j的后綴與排名為i的后綴的LCP。
這樣轉化成了一個新的問題:給你n個數,求其每個子區間中最小值的和。
考慮對答案的貢獻:ai對答案的貢獻是滿足l∈[lpos,i],r∈[i,rpos]的所有區間[l,r],也即ai*(i-lpos+1)*(rpos-i+1),其中lpos是i左側最后一個大于i的,rpos是i右側最后一個大于等于i的。
(左右包含等號的情況不同是為了處理相同的數,防止重復或漏算)
可以用一個單調棧來在線性時間內求出i-lpos+1和rpos-i+1,具體方法見代碼。
最后的最后,需要把用于去重的數組vis清零,注意不能用memset。
#include <cstdio> #include <cstring> #include <algorithm> #define N 500010 #define mod 23333333333333333ll using namespace std; int n , m , sa[N] , r[N] , ws[N] , wa[N] , wb[N] , wv[N] , rank[N] , height[N][21] , log[N] , num[N * 6] , vis[N] , pos[N] , val[N] , sta[N] , top , lp[N] , rp[N]; char str[N]; void da() {int i , j , p , *x = wa , *y = wb;for(i = 0 ; i < m ; i ++ ) ws[i] = 0;for(i = 0 ; i < n ; i ++ ) ws[x[i] = r[i]] ++ ;for(i = 1 ; i < m ; i ++ ) ws[i] += ws[i - 1];for(i = n - 1 ; i >= 0 ; i -- ) sa[--ws[x[i]]] = i;for(p = j = 1 ; p < n ; j <<= 1 , m = p){for(p = 0 , i = n - j ; i < n ; i ++ ) y[p ++ ] = i;for(i = 0 ; i < n ; i ++ ) if(sa[i] - j >= 0) y[p ++ ] = sa[i] - j;for(i = 0 ; i < n ; i ++ ) wv[i] = x[y[i]];for(i = 0 ; i < m ; i ++ ) ws[i] = 0;for(i = 0 ; i < n ; i ++ ) ws[wv[i]] ++ ;for(i = 1 ; i < m ; i ++ ) ws[i] += ws[i - 1];for(i = n - 1 ; i >= 0 ; i -- ) sa[--ws[wv[i]]] = y[i];for(swap(x , y) , x[sa[0]] = 0 , p = i = 1 ; i < n ; i ++ ){if(y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + j] == y[sa[i] + j]) x[sa[i]] = p - 1;else x[sa[i]] = p ++ ;}}for(i = 1 ; i < n ; i ++ ) rank[sa[i]] = i;for(p = i = 0 ; i < n - 1 ; height[rank[i ++ ]][0] = p)for(p ? p -- : 0 , j = sa[rank[i] - 1] ; r[i + p] == r[j + p] ; p ++ ); } int query(int x , int y) {x ++ ;int k = log[y - x + 1];return min(height[x + (1 << k) - 1][k] , height[y][k]); } bool cmp(int a , int b) {return rank[a] < rank[b]; } int main() {int i , j , k , cnt , tot;long long ans;scanf("%d%d%s" , &n , &k , str);for(i = 0 ; i < n ; i ++ ) r[i] = str[i] - 'a' + 1;n ++ , m = 28 , da() , n -- ;for(i = 2 ; i <= n ; i ++ ) log[i] = log[i >> 1] + 1;for(i = 1 ; i <= log[n] ; i ++ )for(j = (1 << i) ; j <= n ; j ++ )height[j][i] = min(height[j][i - 1] , height[j - (1 << (i - 1))][i - 1]);while(k -- ){scanf("%d" , &cnt);tot = 0 , ans = 0;for(i = 1 ; i <= cnt ; i ++ ){scanf("%d" , &num[i]) , num[i] -- ;if(!vis[num[i]]) vis[num[i]] = 1 , pos[++tot] = num[i];}sort(pos + 1 , pos + tot + 1 , cmp);for(i = 1 ; i < tot ; i ++ )val[i] = query(rank[pos[i]] , rank[pos[i + 1]]);sta[0] = top = 0;for(i = 1 ; i < tot ; i ++ ){while(top && val[sta[top]] > val[i]) top -- ;lp[i] = i - sta[top] , sta[++top] = i;}sta[0] = tot , top = 0;for(i = tot - 1 ; i ; i -- ){while(top && val[sta[top]] >= val[i]) top -- ;rp[i] = sta[top] - i , sta[++top] = i;}for(i = 1 ; i < tot ; i ++ ) ans = (ans + (long long)lp[i] * rp[i] * val[i]) % mod;printf("%lld\n" , ans);for(i = 1 ; i <= cnt ; i ++ ) vis[num[i]] = 0;}return 0; }?
?
轉載于:https://www.cnblogs.com/GXZlegend/p/6871613.html
總結
以上是生活随笔為你收集整理的【bzoj3879】SvT 后缀数组+倍增RMQ+单调栈的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++11 并发指南六(atomic 类
- 下一篇: Qt之线程同步(生产者消费者模式 - Q