专题知识点 学习笔记
各种专题新学的知识点有点太多了,还是新开一篇比较好。
AC 自动机,SA,SAM,PAM
毕竟是给自己写的,感觉不太想写怎么构建,随便找点例题一句话题解吧。
二次离线莫队
为什么名字这么高大上啊,我还以为多难呢(
考虑朴素莫队,发现单次加入/删除的复杂度为 \(\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;
}
还有一些,学会了再补。