luogu P5161 WD与数列 SAM 线段树合并 启发式合并
LINK:WD与数列
这道题可谓妙绝 我明白了一个增量统计的原理。
原本的想法是:差分之后 显然长度为1的单独统计 长度为2的以及更多就是字符串之间的匹配问题了。
对差分序列建立SAM 由于第一个是一定匹配的 且后面的大小关系相同 所以可以直接取差分后的来建立SAM.
考虑计算答案 容易想到对于某个节点单独统计答案 那就是right集合上len暴力扫了 可能可以通过45分 我没试过 且这个暴力过于暴力 也不好说明复杂度.
考虑一件事情 其实统计答案的本质是 len.right集合中x,y三者之间造成的贡献。
单独考虑两个x,y 此时没必要强制考虑当前节点的len 因为在之后 他们一定会把1~lenmx 都统计一遍 所以在两个right集第一次相遇时就可以把之后的答案给得到.
所以这个暴力的改进是 每次对于新加入的right集合直接 对已有的right集合计算贡献 且贡献的区间长度为1~len.
两个endpos之间产生贡献 最坏复杂度就是n^2的了.
这个就叫做增量统计贡献.
对于新加入的x统计之前所有的y的贡献 这个东西 可以利用线段树来快速统计 大体上就是判断lenmx的有多少 不是的话就利用|x-y|-1类似的东西统计。
这样就是logn快速统计一个决策的答案了。
不过这个决策的数量每次在合并的时候并没有保证 最坏还是n^2logn的。
考虑每次是两个决策集合合并的过程 利用启发式合并就可以保证每个决策最多被合并logn次了。
这样总复杂度nlog^2.非常的妙。
值得一提的是出题人的初衷是让我们写SA 利用容斥和 优秀的拆分那道题的方法 来统计答案 这样也很妙 这样可以做到nlogn.
不过这种 方法细节较多 没有线段树来的暴力 (理解优秀的拆分的方法就行辣
const int MAXN=300010<<1;
int n,last=1,id,cnt=1;ll ans,sum1,sum2;
int a[MAXN],f[MAXN],len[MAXN],c[MAXN],q[MAXN],w[MAXN],root[MAXN];
map<int,int>t[MAXN];vector<int>g[MAXN];
struct wy{int l,r;ll sum;int cnt;}s[MAXN*40];
inline void insert(int &p,int l,int r,int x)
{
	if(!p)p=++id;
	if(l==r){++c(p);sum(p)+=x;return;}
	int mid=(l+r)>>1;
	if(x<=mid)insert(l(p),l,mid,x);
	else insert(r(p),mid+1,r,x);
	sum(p)=sum(l(p))+sum(r(p));
	c(p)=c(l(p))+c(r(p));
}
inline void insert(int x,int ww)
{
	int p=last;
	int np=last=++cnt;w[last]=last;
	len[np]=len[p]+1;g[last].pb(ww);
	insert(root[last],1,n-1,ww);
	while(p&&!t[p][x])
	{
		t[p][x]=np;
		p=f[p];
	}
	if(!p)f[np]=1;
	else
	{
		int q=t[p][x];
		if(len[q]==len[p]+1)f[np]=q;
		else
		{
			int nq=++cnt;
			t[nq]=t[q];f[nq]=f[q];
			len[nq]=len[p]+1;
			f[q]=f[np]=nq;
			while(p&&t[p][x]==q)
			{
				t[p][x]=nq;
				p=f[p];
			}
		}
	}
}
inline void topsort()
{
	rep(1,cnt,i)++c[len[i]];
	rep(1,cnt,i)c[i]+=c[i-1];
	rep(1,cnt,i)q[c[len[i]]--]=i;
}
inline int ask(int p,int l,int r,int L,int R)
{
	if(!p||L>n-1||R<1||L>R)return 0;
	if(L<=l&&R>=r)return c(p);
	int mid=(l+r)>>1,cnt=0;
	if(L<=mid)cnt+=ask(l(p),l,mid,L,R);
	if(R>mid)cnt+=ask(r(p),mid+1,r,L,R);
	return cnt;
}
inline void ask1(int p,int l,int r,int L,int R)
{
	if(!p||L>n-1||R<1||L>R)return;
	if(!p)return;
	if(L<=l&&R>=r){sum1+=c(p);sum2+=sum(p);return;}
	int mid=(l+r)>>1;
	if(L<=mid)ask1(l(p),l,mid,L,R);
	if(R>mid)ask1(r(p),mid+1,r,L,R);
}
inline int merge(int x,int y,int l,int r)
{
	if(!x||!y)return x|y;
	if(l==r){c(x)+=c(y);sum(x)+=sum(y);return x;}
	int mid=(l+r)>>1;
	l(x)=merge(l(x),l(y),l,mid);
	r(x)=merge(r(x),r(y),mid+1,r);
	sum(x)=sum(l(x))+sum(r(x));
	c(x)=c(l(x))+c(r(x));
	return x;
}
int main()
{
	//freopen("1.in","r",stdin);
	get(n);get(a[1]);
	rep(2,n,i)get(a[i]),insert(a[i]-a[i-1],i-1);
	topsort();
	fep(cnt,2,i)
	{
		int x=q[i],fa=f[x];
		if(fa==1)continue;
		if(g[w[fa]].size()<g[w[x]].size())
		{
			swap(w[fa],w[x]);
			swap(root[fa],root[x]);
		}
		//启发式合并保证复杂度正确.
		for(ui int j=0;j<g[w[x]].size();++j)
		{
			int tn=g[w[x]][j];
			ans+=(ll)ask(root[fa],1,n-1,tn+len[fa]+1,n-1)*len[fa];
			ans+=(ll)ask(root[fa],1,n-1,1,tn-len[fa]-1)*len[fa];
			sum1=sum2=0;ask1(root[fa],1,n-1,tn+1,tn+len[fa]);
			ans+=sum2-sum1*tn-sum1;
			sum1=sum2=0;ask1(root[fa],1,n-1,tn-len[fa],tn-1);
			ans+=sum1*tn-sum2-sum1;
			g[w[fa]].pb(tn);
		}
		root[fa]=merge(root[fa],root[x],1,n-1);
	}
	ans+=(ll)n*(n-1)/2;putl(ans);
	return 0;
}
luogu P5161 WD与数列 SAM 线段树合并 启发式合并的更多相关文章
- 【LUOGU???】WD与数列 sam 启发式合并
		题目大意 给你一个字符串,求有多少对不相交且相同的子串. 位置不同算多对. \(n\leq 300000\) 题解 先把后缀树建出来. DFS 整棵树,维护当前子树的 right 集合. 合并两个集合 ... 
- LUOGU P1438 无聊的数列 (差分+线段树)
		传送门 解题思路 区间加等差数列+单点询问,用差分+线段树解决,线段树里维护的就是差分数组,区间加等差数列相当于在差分序列中l位置处+首项的值,r+1位置处-末项的值,中间加公差的值,然后单点询问就相 ... 
- 【BZOJ2733】永无乡(线段树,启发式合并)
		题意:支持合并,求块内K小数 对于 100%的数据 n≤100000,m≤n,q≤300000 思路:对于每一个块建立一棵动态开点的线段树,暴力(启发式?)合并后二分下就行了 merge用函数的方式写 ... 
- COGS 2638. 数列操作ψ 线段树
		传送门 : COGS 2638. 数列操作ψ 线段树 这道题让我们维护区间最大值,以及维护区间and,or一个数 我们考虑用线段树进行维护,这时候我们就要用到吉司机线段树啦 QAQ 由于发现若干次an ... 
- BZOJ_4636_蒟蒻的数列_线段树+动态开点
		BZOJ_4636_蒟蒻的数列_线段树+动态开点 Description 蒟蒻DCrusher不仅喜欢玩扑克,还喜欢研究数列 题目描述 DCrusher有一个数列,初始值均为0,他进行N次操作,每次将 ... 
- bzoj1396识别子串(SAM+线段树)
		复习SAM板子啦!考前刷水有益身心健康当然这不是板子题/水题…… 很容易发现只在i位置出现的串一定是个前缀串.那么对答案的贡献分成两部分:一部分是len[x]-fa~len[x]的这部分贡献会是r-l ... 
- [BZOJ4552][TJOI2016&&HEOI2016]排序(二分答案+线段树/线段树分裂与合并)
		解法一:二分答案+线段树 首先我们知道,对于一个01序列排序,用线段树维护的话可以做到单次排序复杂度仅为log级别. 这道题只有一个询问,所以离线没有意义,而一个询问让我们很自然的想到二分答案.先二分 ... 
- 线段树:CDOJ1592-An easy problem B (线段树的区间合并)
		An easy problem B Time Limit: 2000/1000MS (Java/Others) Memory Limit: 65535/65535KB (Java/Others) Pr ... 
- 线段树合并&&启发式合并笔记
		这俩东西听起来很高端,实际上很好写,应用也很多~ 线段树合并 线段树合并,顾名思义,就是建立一棵新的线段树保存原有的两颗线段树的信息. 考虑如何合并,对于一个结点,如果两颗线段树都有此位置的结点,则直 ... 
随机推荐
- CF1215D Ticket Game(思维,博弈)
			题目 传送门:https://www.luogu.com.cn/problem/CF1215D Idea 一列数,保证能分成左右两部分,其中有若干个数字被抹掉,两个人轮流填数,如果在把这些空缺的数字填 ... 
- 数据可视化之PowerQuery篇(十一)使用Power BI进行动态帕累托分析
			https://zhuanlan.zhihu.com/p/57763423 上篇文章介绍了帕累托图的用处以及如何制作一个简单的帕累托图,在 PowerBI 中可以很方便的生成,但若仅止于此,并不足以体 ... 
- Python之 爬虫(二十三)Scrapy分布式部署
			按照上一篇文章中我们将代码放到远程主机是通过拷贝或者git的方式,但是如果考虑到我们又多台远程主机的情况,这种方式就比较麻烦,那有没有好用的方法呢?这里其实可以通过scrapyd,下面是这个scrap ... 
- 前端01 /HTML简单简绍
			前端01 /HTML简单简绍 目录 前端01 /HTML简单简绍 1.web服务本质 2.浏览器的工作流程 3.HTML是什么 4.web服务本质 5.HTML文档结构 6.HTML注释 6.标签语法 ... 
- Vue中token的实现
			在学习vue的过程中,正好项目中做的web系统对安全性有要求 转载自https://www.jianshu.com/p/d1a3fb71eb99 总:通过axios,vuex,及自定义的方法实现.以下 ... 
- OSCP Learning Notes - Exploit(2)
			Compiling an Exploit Exercise: samba exploit 1. Search and download the samba exploit source code fr ... 
- P4017 最大食物链计数 (拓扑排序)
			看到拓扑排序感觉非常遥远的复杂,不喜欢图.看了拓扑排序的原理,很像广搜. 以本题样例为例: 了解一下 出度 和 入度 5的出度为3 入度为 0 ,3的出度为2 入度为2…… for循环 找到秃头 5 ... 
- 评测Loki日志工具
			评测Loki日志工具 目录 评测Loki日志工具 部署Loki 配置grafana 总结: 优势: 劣势: 本文仅对Loki进行简单评测,不涉及原理和细节. 部署Loki Loki是grafana团队 ... 
- 【SpringBoot】 中时间类型 序列化、反序列化、格式处理
			[SpringBoot] 中时间类型 序列化.反序列化.格式处理 Date yml全局配置 spring: jackson: time-zone: GMT+8 date-format: yyyy-MM ... 
- win7下建立docker共享文件夹
			前言 建立本机(win7)和VirtualBox中docker虚拟机的共享文件夹,注:下面的命令都是以root身份运行的,使用sudo -i切换到root身份,如无法切换,请自行在命令前加上sudo命 ... 
