谢特——后缀数组+tire 树

题目

【题目描述】

由于你成功地在 $ \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 }

原文地址:https://www.cnblogs.com/chmwt/p/10660397.html

时间: 2024-10-08 18:51:02

谢特——后缀数组+tire 树的相关文章

BZOJ 1396: 识别子串( 后缀数组 + 线段树 )

这道题各位大神好像都是用后缀自动机做的?.....蒟蒻就秀秀智商写一写后缀数组解法..... 求出Height数组后, 我们枚举每一位当做子串的开头. 如上图(x, y是height值), Heights数组中相邻的3个后缀, 假如我们枚举s2的第一个字符为开头, 那我们发现, 长度至少为len = max(x, y)+1, 才能满足题意(仅出现一次). 这个很好脑补...因为s2和其他串的LCP是RMQ, 肯定会<=LCP(s1,s2)或<=LCP(s2,s3). 然后就用len去更新s2中

中文分词系列(二) 基于双数组Tire树的AC自动机

秉着能偷懒就偷懒的精神,关于AC自动机本来不想看的,但是HanLp的源码中用户自定义词典的识别是用的AC自动机实现的.唉-没办法,还是看看吧 AC自动机理论 Aho Corasick自动机,简称AC自动机,要学会AC自动机,我们必须知道什么是Trie,也就是字典树.Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高.之

BZOJ 2251 2010Beijing WC 外星联络 后缀数组/Trie树

题目大意 给出一个字符串,问这个字符串中出现过1次以上的子串的个数,按照子串的字典序输出. 思路 由于数据范围过小,这个题有两个解法. 基本的想法就是用后缀数组来进行后缀的排序,之后按照height数组扫就可以了.应该是挺快的. 但是注意到数据范围只有3000,因此我们只需要弄出所有的后缀拿出来建立一颗后缀Trie树就行了.最后DFS一次树种的所有节点. CODE SuffixArray #include <cstdio> #include <cstring> #include &

【XSY1551】往事 广义后缀数组 线段树合并

题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\)最长公共前缀,\(LCS=\)最长公共后缀 \(1\leq n\leq 200000\),字符集为\(\{0\ldots 300\}\) 题解 我们先看看这个\(LCP(s_u,s_v)\)怎么求 广义后缀自动机不行.广义后缀树可能可以,但我不会.广义后缀数组可以.然后我就开始手推广义后缀数组 广义

HDU-6704 K-th occurrence(后缀数组+主席树)

题意 给一个长度为n的字符串,Q次询问,每次询问\((l,r,k)\) , 回答子串\(s_ls_{l+1}\cdots s_r\) 第\(k\) 次出现的位置,若不存在输出-1.\(n\le 1e5,Q\le 1e5\) 分析 查询子串第 k 次出现的位置,很容易想到要用处理字符串的有力工具--后缀数组. 那么该怎么用呢?我们先把样例的字符串的每个后缀排个序,然后对样例进行模拟 原串:aaabaabaaaab 排名 后缀 位置 1 aaaab 8 2 aaab 9 3 aaabaabaaab

2019 CCPC 网络赛第三题 K-th occurrence 后缀数组+划分树+ST表+二分

题意:给你一个长度为n的字符串,每次询问给出三个数:L , R , K,表示原串 L 到 R 的子串在原串第K次出现的首字母的位置 解题思路:对子串的大量操作,不难想到后缀数组(后缀树/后缀自动机不会,所以没想到),注意到子串s[L.....R]必然是某一个后缀的前缀,所以所有前缀是该子串的后缀的排名(即rank数组的值)必定连续,也就是说在后缀数组(sa数组)中,下标是连续的,那么就是求区间第K大了(因为sa数组的值代表的是在字符串中的位置)(这里区间第K大我用划分树求),至于这一段区间的起点

hdu6704 后缀数组+主席树+ST +二分

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6704 推荐博客:https://www.cnblogs.com/1625--H/p/11403199.html 题意:输入t,接下来t组数据,每组数据输入n和q,n代表字符串长度,q代表查询次数,接下来一行输入长度为n的字符串,最后再输入q行查询,每一个查询有l,r,k,查询字符串中在区间[l,r]里的这个子串第k次出现的位置,位置就是出现这个子串的首字母位置,如果没有第k次,则输出-1. 比赛时我

HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)

题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点,求第k大的下标即可. 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10,mod=998244353; 5 char buf[N]; 6 int s[N],sa[

loj6198谢特 后缀数组+并查集+Trie

先把问题放在后缀数组上考虑 已知两个数组a b,求min(a[i],...,a[j])+(b[i]^b[j])的最大值 套路题 初始每个点都是一个小连通块 把a按从大到小的顺序加入,计算当前加入边作为min的贡献: 每次加入会把两个连通块联通,答案就是两边连通块各出一个数能得到的异或和最大值 我:这不是线性基吗 miaom:mdzz,只能有两个数 我:蛤,好难啊,怎么做啊 miaom:Trie啊 我:哦 没了 1 #include <bits/stdc++.h> 2 #define N 500