1. 程式人生 > >謝特——後綴數組+tire 樹

謝特——後綴數組+tire 樹

題解 最大值 std cout style dep pan sed 合並

題目

【題目描述】

由於你成功地在 $ \text{1 s} $ 內算出了上一題的答案,英雄們很高興並邀請你加入了他們的遊戲。然而進入遊戲之後你才發現,英雄們打的遊戲和你想象的並不一樣……

英雄們打的遊戲是這樣的:首先系統會產生(**註意不一定是隨機產生**)一個字符串,然後每個英雄就會開始根據自己分到的任務計算這個字符串的某些特征,誰先算出自己的答案誰就是勝者。

由於打遊戲的英雄比較多,因此英雄們分到的任務也就可能很奇怪。比如你分到的這個任務就是這樣:

定義這個字符串以第 $ i $ 個字符開頭的後綴為後綴 $ i $ (編號從 $ 1 $ 開始),每個後綴 $ i $ 都有一個權值 $ w_i $ ,同時定義兩個後綴 $ i,j $ ($ i\ne j $) 的貢獻為它們的最長公共前綴長度加上它們權值的異或和,也就是 $ \mathrm{LCP}(i,j)+(w_i \mathbin{\text{xor}} w_j) $ 。而你的任務就是,求出這個字符串的所有後綴兩兩之間貢獻的最大值。

【輸入格式】

第一行一個正整數 $ n $,表示字符串的長度。
第二行一個僅包含小寫英文字母的字符串,即系統產生的字符串。
第三行 $ n $ 個非負整數 $ w_i $,分別表示後綴 $ 1 $ ~ $ n $ 的權值。

【輸出格式】

一行一個整數表示答案。

【樣例輸入】

7
acbabac
0 1 5 6 4 2 3

【樣例輸出】

7

【樣例解釋】

後綴 $ 1 $ 和後綴 $ 4 $ 的貢獻是 $ 1+(0\;\text{xor}\;6)=7 $ ,不難驗證它們的貢獻確實是所有可能的貢獻中最大的。

【數據範圍與提示】

對於 $ 30\% $ 的數據,$ n\le 5\times 10^3 $;

對於另 $ 30\% $ 的數據,保證字符串是隨機生成的;
對於另 $ 10\% $ 的數據,$ w_i=0 $;
對於另 $ 10\% $ 的數據,$ w_i\le 1 $;
對於 $ 100\% $ 的數據,$ n\le 10^5 $,$ w_i< n $ 。

題解

求任意兩個後綴的 LCP 很容易想到後綴數組

記排序後的兩個相鄰後綴 $ i-1,i $ 的 LCP 為 $ height[i] $

那麽任意的兩個後綴 $ i,j $ 的 LCP 為 $ min_{k=i}^{j}height[k] $

至於求 $W$ 的異或值考慮在 tire 樹上貪心

當 $ height[i] $ 為 $[l,r] $ 的最小值時才會對該區間有影響,那麽考慮如何用 $ height[i] $ 來更新答案

將 $ height[i] $ 從大到小排序後,合並 $ P_i $ 和 $ P_{i-1} $ 屬於的兩個區間 $ [L_{P_i},R_{p_i}] $ 和 $ [L_{P_{i-1}},R_{P_{i-1}}] $,此時保證 $ height[i] $ 為兩個區間中的最小值(因為比 $ i $ 大的已經合並了)

然後在 tire 樹上啟發式合並兩個區間即可,貪心選取答案

時間效率:$ O(n \log n+n \log^2n)$

至於 SA 的排序可以用倍增法或者二分哈希都可以(也就多一個 $ \log $,反正啟發式合並也要 $ \log^2 $)

為什麽我一點都沒有感覺到套路,可能是題寫太少了

代碼

技術分享圖片
 1 #include<bits/stdc++.h>
 2 #define LL long long
 3 #define _(d) while(d(isdigit(ch=getchar())))
 4 using namespace std;
 5 int R(){
 6     int x;bool f=1;char ch;_(!)if(ch==-)f=0;x=ch^48;
 7     _()x=(x<<3)+(x<<1)+(ch^48);return f?x:-x;}
 8 const int N=2e5+5;
 9 int n,m,w[N],p[N],ht[N],rak[N],tp[N],sa[N],tax[N],ans;
10 char ch[N];
11 void Qsort(){
12     for(int i=0;i<=m;i++)tax[i]=0;
13     for(int i=1;i<=n;i++)tax[rak[tp[i]]]++;
14     for(int i=1;i<=m;i++)tax[i]+=tax[i-1];
15     for(int i=n;i>=0;i--)sa[tax[rak[tp[i]]]--]=tp[i];
16 }
17 void SA(){
18     m=26,Qsort();
19     for(int l=1,p=0;l<=n;l<<=1){
20         for(int i=n-l+1;i<=n;i++)tp[++p]=i;
21         for(int i=1;i<=n;i++)if(sa[i]>l)tp[++p]=sa[i]-l;
22         Qsort(),swap(rak,tp);
23         rak[sa[1]]=p=1;
24         for(int i=2;i<=n;i++)
25             rak[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+l]==tp[sa[i]+l])?p:++p;
26         if(p>n)break;
27         m=p+1,p=0;
28     }
29     int k=0;
30     for(int i=1,j;i<=n;i++){
31         j=sa[rak[i]-1];
32         if(k)k--;
33         while(ch[j+k]==ch[i+k])k++;
34         ht[rak[i]]=k;
35     }
36 }
37 bool cmp(int a,int b){return ht[a]>ht[b];}
38 int li[N],ri[N],fa[N],rt[N],tot,tr[N*50][2];
39 int query(int k,int dep,int val){
40     if(!~dep)return 0;
41     if(tr[k][((val>>dep)&1)^1])
42         return (1<<dep)+query(tr[k][((val>>dep)&1)^1],dep-1,val);
43     else return query(tr[k][(val>>dep)&1],dep-1,val);
44 }
45 void insert(int &k,int dep,int val){
46     if(!k)k=++tot;
47     if(~dep)insert(tr[k][(val>>dep)&1],dep-1,val);
48 }
49 int merge(int x,int y){
50     int res=0;
51     if(ri[x]-li[x]<ri[y]-li[y])swap(x,y);
52     for(int i=li[y];i<=ri[y];i++)
53         res=max(res,query(rt[x],17,w[sa[i]]));
54     for(int i=li[y];i<=ri[y];i++)
55         insert(rt[x],17,w[sa[i]]);
56     fa[y]=x,li[x]=min(li[x],li[y]),ri[x]=max(ri[x],ri[y]);
57     return res;
58 }
59 int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
60 int main(){
61     n=R(),scanf("%s",ch+1);
62     for(int i=1;i<=n;i++)
63         rak[i]=ch[i]-a,p[i]=tp[i]=i,w[i]=R();
64     SA(),sort(p+2,p+n+1,cmp);
65     for(int i=1;i<=n;i++)
66         li[i]=ri[i]=fa[i]=i,insert(rt[i],17,w[sa[i]]);
67     for(int i=2;i<=n;i++)
68         ans=max(ans,ht[p[i]]+merge(find(p[i]-1),find(p[i])));
69     cout<<ans<<endl;
70     return 0;
71 }
View Code

謝特——後綴數組+tire 樹