专题知识点 学习笔记

各种专题新学的知识点有点太多了,还是新开一篇比较好。

AC 自动机,SA,SAM,PAM

毕竟是给自己写的,感觉不太想写怎么构建,随便找点例题一句话题解吧。

二次离线莫队

为什么名字这么高大上啊,我还以为多难呢(

P4887 【模板】莫队二次离线(第十四分块(前体))

考虑朴素莫队,发现单次加入/删除的复杂度为 \(\mathcal{O}(\dbinom{14}{k})\),显然不能接受。

考虑一个贡献的拆。假如当前区间为 \([l,r]\),现在需要把右端点拓展到 \(R\)。记 \(f(i,l,r)\) 表示 \(\sum\limits_{j=l}^r[\text{popcount}(a_i\oplus a_j)=k]\),那么新增加的答案就是 \(\sum\limits_{i=r+1}^R f(i,l,i-1)\)注意到 f 具有可减性,可以写成 \(f(i,1,i-1)-f(i,1,l-1)\)。前面的可以预处理,而后面的右端点都是 \(l-1\),可以离线下来扫描线。其余情况类似。空间复杂度 \(\mathcal{O}(n+V)\),时间复杂度 \(\mathcal{O}(n\dbinom{14}{k}+n\sqrt{n})\),因为莫队端点移动次数是 \(n\sqrt{n}\) 的。

总结一下,二次离线需要的条件是可以离线(好像是废话),且维护信息具有可减性。记 \(k\) 表示单次移动端点的复杂度,二离可以将其从 \(\mathcal{O}(nk\sqrt{n})\) 优化到 \(\mathcal{O}(nk+n\sqrt{n})\)

感觉现阶段做到的要用二离的题都挺板的啊。

例题不多,大概就 P4887,P5398,P5047,P5501 这几道吧。

可并堆

“我要引导可并堆之力了!”

P3377 【模板】左偏树/可并堆。当然你可以启发式合并,复杂度 \(\mathcal{O}(n\log^2 n)\),但这不是重点。

先说斜堆。考虑在合并两个以 \(a,b\) 为根的小根堆时,如果 \(val(a)<val(b)\),就要把 \(b\) 合并到 \(ls(a)\)\(rs(a)\) 的子树内。但是如果每次都向同一边合并,两边的结构会很不平衡。考虑在合并完后交换 \(ls(a)\)\(rs(a)\),于是这颗二叉搜索树的期望高度就是 \(\log n\) 的,可以可持久化什么的。

然后是左偏树。考虑对每个点维护 \(d_i\) 表示最近的叶子节点到 \(i\) 的距离,特别的,叶子节点的 \(d\) 为 -1。左偏树即一颗满足 \(d_{ls(i)}\ge d_{rs(i)}\) 的二叉搜索树。考虑合并的时候每次把大的点往小的点的右儿子上合并,并在返回的时候维护左偏树的性质。即如果合并后 \(d_{ls(i)}<d_{rs(i)}\) 就交换 \(ls(i),rs(i)\)。容易发现这样是 \(\mathcal{O}(h)=\mathcal{O}(\log n)\) 的。但是左偏树左子树的高度并不是 \(\mathcal{O}(\log n)\) 而是 \(\mathcal{O}(\log n)\) 的,所以在找一个点所在堆时需要路径压缩。

还有一些神秘的堆,比如插入后随机交换左右儿子,感觉挺对的?那可是随机啊.jpg

数据结构优化建图

reference:常见优化建图技巧

考虑一个题:CF786B。思考如何维护单点/区间向单点/区间连边:

  • 点对点。直接连就行。

  • 点对区间。开一颗线段树,每个节点代表一段区间,父亲向儿子连权为 \(0\) 的边。点对区间连边时直接向 \(\log n\) 个代表点连边即可。

  • 区间对点。与上边类似,开一颗线段树,儿子向父亲连权为 \(0\) 的边。从 \(\log n\) 个代表点连出去即可。

  • 区间对区间。新建一个虚点为这两个区间做一个中转即可。

回到这个题。这个题需要同时维护点对点,区间对点,点对区间连边,求最短路。开两颗线段树,父亲向儿子连边的是第一颗,儿子向父亲连边的是第二颗。

  • 点对点,从第二颗的叶子连到第一颗的叶子。

  • 点对区间,从第二颗的叶子连到第一颗的区间。

  • 区间对点,从第二颗的区间连到第一颗的叶子。

正确性显然。最短路直接从第二颗线段树上 \(s\) 对应的叶子开始跑即可。注意到此时两颗线段树上对应叶子节点间都要互连权为 \(0\) 的边,因为他们代表的是同一个点。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18,N=5e5;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,w,nxt;
}e[4000005];
int tot,head[1000005];
void add(int u,int v,int w){
	e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int lf[100005];
void build(int l,int r,int p){
	if(l==r){lf[l]=p;return;}
	int mid=(l+r)>>1;
	add(p,ls(p),0),add(p,rs(p),0);
	add(ls(p)+N,p+N,0),add(rs(p)+N,p+N,0);
	build(l,mid,ls(p));build(mid+1,r,rs(p));
}
void solve(int l,int r,int p,int op,int u,int L,int R,int w){
	if(L<=l&&r<=R){
		if(op==2)add(lf[u]+N,p,w);
		else add(p+N,lf[u],w);
		return;
	}
	int mid=(l+r)>>1;
	if(L<=mid)solve(l,mid,ls(p),op,u,L,R,w);
	if(R>mid)solve(mid+1,r,rs(p),op,u,L,R,w);
}
int d[1000005],vis[1000005];
void dijkstra(int s){
	priority_queue<pii,vector<pii>,greater<pii> >q;
	for(int i=1;i<=N*2;i++)d[i]=inf,vis[i]=0;
	d[s]=0;q.push(mk(d[s],s));
	while(!q.empty()){
		int u=q.top().se;q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w;
			if(d[u]+w<d[v])d[v]=d[u]+w,q.push(mk(d[v],v));
		}
	}
}
signed main(){
	int n=read(),m=read(),s=read();build(1,n,1);
	for(int i=1;i<=n;i++)add(lf[i],lf[i]+N,0),add(lf[i]+N,lf[i],0);
	while(m--){
		int op=read();
		if(op==1){
			int u=read(),v=read(),w=read();
			add(lf[u]+N,lf[v],w);
		}
		else{
			int u=read(),l=read(),r=read(),w=read();
			solve(1,n,1,op,u,l,r,w);
		}
	}
	dijkstra(lf[s]+N);
	for(int i=1;i<=n;i++)printf("%lld ",((d[lf[i]]>=inf)?-1:d[lf[i]]));
	return 0;
}

还有一些,学会了再补。

posted @ 2023-11-22 19:28  xx019  阅读(21)  评论(0编辑  收藏  举报