LOJ#6198. 谢特(SAM+01Trie树合并)
题解
SAM+01Trie树合并的模板题(两样东西我都不太会,写了我一下午,我太菜了5555……)
先考虑反着建SAM,得到的fail树就是原串的后缀树
后缀树上两个点的LCA的endpos集合的最大长度就是这两个点的LCP长度
这样我们就解决了第一个值LCP(x,y)
考虑dfs一遍后缀树,那么每到一个节点,它的子树中的所有点对的贡献中的LCP(x,y)都是固定的了
如何求一个子树中所有节点权值的两两异或最大值呢?
用01Trie树来进行贪心,先尽量走不同方向,再走相同方向
但是如果在每个节点都开一个01Trie树并把所有子节点都加进来,复杂度会爆炸
我们考虑把所有儿子的01Trie树来合并到一起
合并的时候可以不用新建节点来可持久化(所以为什么放在了可持久化作业里a。。。)
(如果建了新点就是可持久化01Trie树合并,空间复杂度为O(nlogn))
(事实上大部分题都是需要新建点的,否则会出许多奇奇怪怪的问题)
这样的复杂度分析类似于线段树合并,是O(nlogn)的
因为我们只有在两棵线段树都存在节点的时候合并两棵线段树的答案
一次合并的复杂度其实是较小的线段树的节点个数(查询的复杂度也是)
查询的时候应该在合并之前查询,否则时间复杂度会从均摊的O(nlogn)退化为O(n^2)
这是有的人就会问了,“一次合并的复杂度其实是较小的线段树的节点个数”,那么总的合并次数就应该是O(nlogn)的
而set启发式合并也是只合并了O(nlogn)次,那为什么set合并的复杂度为O(nlog^2n)呢?
因为线段树合并一次是均摊O(minsiz*c)的(minsiz为较小的点集的大小,c是常数)
而set合并是一个一个暴力插入的是O(minsiz*log(maxsiz))的
各种合并算法的时间复杂度分析
这里不得不提到一些关于合并的问题
长链剖分合并两条链时的合并次数是均摊O(n)的
因为一条长度为5的链合并到一条长度为10的链得到的是长度为10的链
也就是说每条链只会在向链顶父亲合并时会贡献O(链长)的合并次数
而总链长为O(n),时间复杂度也就是O(n*一次insert的复杂度)
重链剖分合并两棵子树是的合并次数是均摊O(nlogn)的
因为一棵大小为5的子树合并到一棵大小为10的子树得到的是大小为15的子树
也就是说每个top点只会在向其父亲合并时会贡献O(子树大小)的合并次数
均摊到每个点的身上,就相当于每个点都向上爬链,一直爬到根,经过的轻边次数就是它在合并中贡献的合并次数
由于一个点到根路径最多经过O(logn)条轻边,所以总合并次数就是均摊O(nlogn)
总时间复杂度就是O(nlogn*一次insert的复杂度)
其实启发式合并的原理与这个大同小异
本题代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 200005
int fa[N],ch[N][26],mxlen[N],id[N],last,sz;
void extend(int x,int pos)
{int np,p,nq,q;p=last;np=++sz;mxlen[np]=mxlen[p]+1;id[np]=pos;for(;p!=-1&&!ch[p][x];p=fa[p])ch[p][x]=np;if(p==-1)fa[np]=0;else{q=ch[p][x];if(mxlen[q]==mxlen[p]+1)fa[np]=q;else{nq=++sz;mxlen[nq]=mxlen[p]+1;memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];for(;p!=-1&&ch[p][x]==q;p=fa[p])ch[p][x]=nq;fa[np]=fa[q]=nq;}}last=np;
}
char s[N];int w[N];
int fir[N],to[N],nxt[N],cnt;
void adde(int a,int b){to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;}
struct node{int ch[2];
}a[N<<5];
int T[N],tot;
void insert(int &i,int x,int d)
{if(!i)i=++tot;if(d==-1)return;insert(a[i].ch[(x>>d)&1],x,d-1);
}
int ans;
void query(int i,int j,int sum,int d)
{if(d==-1){ans=max(sum,ans);return;}if((a[i].ch[0]&&a[j].ch[1])||(a[i].ch[1]&&a[j].ch[0])){if(a[i].ch[0]&&a[j].ch[1])query(a[i].ch[0],a[j].ch[1],sum+(1<<d),d-1);if(a[i].ch[1]&&a[j].ch[0])query(a[i].ch[1],a[j].ch[0],sum+(1<<d),d-1);}else{if(a[i].ch[0]&&a[j].ch[0])query(a[i].ch[0],a[j].ch[0],sum,d-1);if(a[i].ch[1]&&a[j].ch[1])query(a[i].ch[1],a[j].ch[1],sum,d-1);}
}
int merge(int i,int j)
{if(!i||!j)return i+j;a[i].ch[0]=merge(a[i].ch[0],a[j].ch[0]);a[i].ch[1]=merge(a[i].ch[1],a[j].ch[1]);return i;
}
int siz[N];
void solve(int u)
{if(id[u])insert(T[u],w[id[u]],16),siz[u]=1;for(int v,p=fir[u];p;p=nxt[p]){v=to[p];solve(v);//if(siz[u]<siz[v])swap(T[u],T[v]);query(T[u],T[v],mxlen[u],16);T[u]=merge(T[u],T[v]);siz[u]+=siz[v];}
}
int main()
{last=0;fa[0]=-1;int n,i;scanf("%d%s",&n,s+1);for(i=n;i>=1;i--)extend(s[i]-'a',i);for(i=1;i<=n;i++)scanf("%d",&w[i]);for(i=1;i<=sz;i++)adde(fa[i],i);solve(0);printf("%d\n",ans);
}
LOJ#6198. 谢特(SAM+01Trie树合并)相关推荐
- CF1037H Security——SAM+线段树合并
又是一道\(SAM\)维护\(endpos\)集合的题,我直接把CF700E的板子粘过来了QwQ 思路 如果我们有\([l,r]\)对应的\(SAM\),只需要在上面贪心就可以了.因为要求的是字典序比 ...
- P4770-[NOI2018]你的名字【SAM,线段树合并】
正题 题目链接:https://www.luogu.com.cn/problem/P4770 题目大意 给出一个长度为nnn的字符串SSS.qqq次询问给出一个串TTT和一个区间[L,R][L,R][ ...
- CF700E-Cool Slogans【SAM,线段树合并,dp】
正题 题目链接:https://www.luogu.com.cn/problem/CF700E 题目大意 给出一个字符串SSS,求一个最大的kkk使得存在kkk个字符串其中s1s_1s1是SSS的子 ...
- 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree...
原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...
- 2021CCPC华为云挑战赛:HDU 7091 重叠的子串(SAM + 线段树合并)
重叠的子串 给定一个长度为n(1≤∣s∣≤105)n(1 \le \mid s \mid \le 10 ^ 5)n(1≤∣s∣≤105)的只由小写字母构成的字符串sss,有m,(1≤m≤106)m, ...
- CF666E-Forensic Examination【广义SAM,线段树合并】
正题 题目链接:https://www.luogu.com.cn/problem/CF666E 解题思路 给出一个串SSS和nnn个串TiT_iTi.mmm次询问Sa∼bS_{a\sim b}Sa∼ ...
- YbtOJ#532-往事之树【广义SAM,线段树合并】
正题 题目链接:https://www.ybtoj.com.cn/problem/532 题目大意 给出nnn个点的一个TrieTrieTrie树,定义SxS_xSx表示节点xxx代表的字符串 求m ...
- CF204E-Little Elephant and Strings【广义SAM,线段树合并】
正题 题目链接:https://www.luogu.com.cn/problem/CF204E 题目大意 nnn个字符串的一个字符串集合,对于每个字符串求有多少个子串是这个字符串集合中至少kkk个字符 ...
- P4770:你的名字(SAM、线段树合并)
文章目录 前言 解析 前言 1000A快乐!!!awa 没有想象中的那么恶心. 解析 先考虑每次都询问 [1,n][1,n][1,n] 如何做. 正难则反,用T所有本质不同串数量减去是S串子串又是T的 ...
- UOJ #395 BZOJ 5417 Luogu P4770 [NOI2018]你的名字 (后缀自动机、线段树合并)
NOI2019考前做NOI2018题.. 题目链接: (bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=5417 (luogu) http ...
最新文章
- atitit.复合变量,也就是类似$$a的变量的原理与实现 java c#.net php js
- python3 if else 简洁写法 三元运行
- pytorch 加载模型 模型大小测试速度
- GitHub如何删除一个repository(仓库)
- Linux的基础知识——mmap父子通信进程和匿名通信
- uploadify插件html5,免费的HTML5版uploadify送上
- 【软件测试】软件测试与概率
- 数据结构栈之火车出站小结
- Android 系统(166)---GMO版本最近应用列表界面显示模糊的解决方案
- ORA-01747: user.table.column, table.column 或列说明无效
- 如何使keystone更有效率
- matplotlib 对称图_Python入门向:Matplotlib自救指南篇,从此做图不求人(一)
- linux 命令行参数解析,Linux C下的命令行参数解析
- 9小时速返地球!刚刚,神舟十三号返回舱平安降落,三位航天员“感觉良好”...
- leaflet地图原理_Web地图呈现原理
- 什么是通配符 计算机网络,通配符
- oppo手机显示服务器连接错误,OPPO手机连不上wifi怎么办?OPPO连不上wifi的四种解决方法...
- python roundup 和 rounddown
- 美丽的日本与我(川端康成在诺贝尔文学奖颁奖典礼上的演讲词)
- 《机器学习实战》(七)-- LinearRegression