谢特——后缀数组+tire 树
Posted chmwt
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了谢特——后缀数组+tire 树相关的知识,希望对你有一定的参考价值。
题目
【题目描述】
由于你成功地在 $ ext{1 s} $ 内算出了上一题的答案,英雄们很高兴并邀请你加入了他们的游戏。然而进入游戏之后你才发现,英雄们打的游戏和你想象的并不一样……
英雄们打的游戏是这样的:首先系统会产生(**注意不一定是随机产生**)一个字符串,然后每个英雄就会开始根据自己分到的任务计算这个字符串的某些特征,谁先算出自己的答案谁就是胜者。
由于打游戏的英雄比较多,因此英雄们分到的任务也就可能很奇怪。比如你分到的这个任务就是这样:
定义这个字符串以第 $ i $ 个字符开头的后缀为后缀 $ i $ (编号从 $ 1 $ 开始),每个后缀 $ i $ 都有一个权值 $ w_i $ ,同时定义两个后缀 $ i,j $ ($ i e j $) 的贡献为它们的最长公共前缀长度加上它们权值的异或和,也就是 $ mathrm{LCP}(i,j)+(w_i mathbin{ ext{xor}} w_j) $ 。而你的任务就是,求出这个字符串的所有后缀两两之间贡献的最大值。
【输入格式】
第一行一个正整数 $ n $,表示字符串的长度。
第二行一个仅包含小写英文字母的字符串,即系统产生的字符串。
第三行 $ n $ 个非负整数 $ w_i $,分别表示后缀 $ 1 $ ~ $ n $ 的权值。
【输出格式】
一行一个整数表示答案。
【样例输入】
7
acbabac
0 1 5 6 4 2 3
【样例输出】
7
【样例解释】
后缀 $ 1 $ 和后缀 $ 4 $ 的贡献是 $ 1+(0; ext{xor};6)=7 $ ,不难验证它们的贡献确实是所有可能的贡献中最大的。
【数据范围与提示】
对于 $ 30\% $ 的数据,$ nle 5 imes 10^3 $;
对于另 $ 30\% $ 的数据,保证字符串是随机生成的;
对于另 $ 10\% $ 的数据,$ w_i=0 $;
对于另 $ 10\% $ 的数据,$ w_ile 1 $;
对于 $ 100\% $ 的数据,$ nle 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 }
以上是关于谢特——后缀数组+tire 树的主要内容,如果未能解决你的问题,请参考以下文章