搬luogu的文章之近期进度小结
一亿年前写的,忘挪了。
推到淀粉质才发现前面好多都没总结过,怕掺水就提两句。
不过现在确实发现不用写那么详细,又不是给别人看。
基环树
基环树就不提了,手玩样例该咋做咋就就可以,大多是细节题没什么技巧。
笛卡尔树
这个东西很抽象,这里说一下定义。
笛卡尔树是一种二叉树,每一个结点由一个键值二元组 (k,w) 构成。要求 k 满足二叉搜索树的性质,而 w 满足堆的性质。一个有趣的事实是,如果笛卡尔树的 k,w 键值确定,且 k 互不相同,w 互不相同,那么这个笛卡尔树的结构是唯一的。
与其他数据结构不同,在我看来这个东西是题目"模型正好满足笛卡尔树"才去使用,而不是"考虑用笛卡尔树维护",换句话说就是使用范畴很抽象,考场上真出了很难想到并做出来。
制作这种结构的平衡树可以用维护右链的方式
stac[1]=1;
for(int i=2;i<=n;i++){
while(a[stac[top]]>a[i]&&top)--top;
if(!top)ls[i]=stac[top+1];
else ls[i]=rs[stac[top]],rs[stac[top]]=i;
stac[++top]=i;
}
方法和虚树是类似的,不过这个更简单。
这两道题分别展示了笛卡尔树键值的作用,翻转键值可以使你的笛卡尔树解决一些很骚的问题。
笛卡尔树可以反应数据的函数形式,具体的可以把数据的导数和端点值大小关系反映出来。
一种形状的数据的笛卡尔树是唯一的,本题中就运用到了这个技巧。
显然这个相似序列的定义就是每一位在序列中的大小与模版序列一致。进而他俩的笛卡尔树是一致的。
现在问你
既然要求笛卡尔树同构,那么给出笛卡尔树每个节点的字数大小
这个题思路是真的叼,dyc能自己想出来,令人感叹。
观察(颓题解)得,这个不完全的矩阵可以构造成一棵笛卡尔树。
我们发现,这样构成的若干个矩形正好对应小根笛卡尔树上的所有节点,每次递归处理的两个小联通块正是当前节点的两个儿子。根据定义,我们可以知道,对于节点
建好笛卡尔树后在笛卡尔树上进行树形dp,相当于每个节点形成的小矩阵的节点都应该行列没有共点,父节点的子节点会占用它宽度的列数导致不能放。
然后dp。
最后合并即可。
这个题倒是简单,发现老鼠洞形成了一棵笛卡尔树,造好之后跑一遍欧拉序生成01串,然后hash。
还有一题我纯颓的,没脸放这了。
扫描线
就是用线段树解决计算几何问题。
如何处理平面内一群矩形的面积交?
考虑用面积的朴素定义,
我们维护坐标系的
放个代码方便复习。
#include<bits/stdc++.h>
#define MAXN 1000005
#define int long long
using namespace std;
int n;
struct node{
int x1,x2,y;
int opt;
}sq[MAXN<<1];
inline bool cmp(node a,node b){
if(a.y==b.y)return a.opt>b.opt;
return a.y<b.y;
}
int mp[MAXN<<2];
int ans,tmp;
struct Segment_Tree{
#define ls(p) p<<1
#define rs(p) p<<1|1
struct TREE{
int l,r;
int val,tag;//维护一个当前x段的覆盖层数和贡献,只要有层数就有贡献,没层数就没贡献
}tree[MAXN<<3];
inline void build(int l,int r,int p){
tree[p].l=l,tree[p].r=r,tree[p].val=tree[p].tag=0;
if(l==r)return;
int mid=l+r>>1;
build(l,mid,ls(p));
build(mid+1,r,rs(p));
}
inline void push_up(int p){//主要看看pushup即可
if(tree[p].tag)tree[p].val=mp[tree[p].r+1]-mp[tree[p].l];
else{
tree[p].val=tree[ls(p)].val+tree[rs(p)].val;
}
}
inline void modify(int l,int r,int k,int p){
if(mp[tree[p].r+1]<=l||mp[tree[p].l]>=r)return;
if(mp[tree[p].l]>=l&&mp[tree[p].r+1]<=r){
tree[p].tag+=k;
push_up(p);
return;
}
// printf("nowmid=%lld,id=%lld,tree.l=%lld,tree.r=%lld\n",mid,p,tree[p].l,tree[p].r);
modify(l,r,k,ls(p));
modify(l,r,k,rs(p));
push_up(p);
}
}ST;
signed main(){
scanf("%lld",&n);
for(int i=1,x1,x2,y1,y2;i<=n;i++){
scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
sq[i]=(node){x1,x2,y1,1};
sq[i+n]=(node){x1,x2,y2,-1};
mp[i]=x1,mp[i+n]=x2;
}
sort(mp+1,mp+1+n*2);
tmp=unique(mp+1,mp+1+2*n)-mp-1;
//printf("tmp=%lld\n",tmp);
sort(sq+1,sq+1+n*2,cmp);
ST.build(1,tmp-1,1);
//printf("ced\n");
for(int i=1;i<n*2;i++){
// printf("solving %lld %lld %lldf\n",sq[i].x1,sq[i].x2,sq[i].opt);
ST.modify(sq[i].x1,sq[i].x2,sq[i].opt,1);
ans+=ST.tree[1].val*(sq[i+1].y-sq[i].y);
}
printf("%lld",ans);
return 0;
}
现在让你计算那坨矩形的周长。
同样的分析方法,矩形的下底面和上底面都会对当前答案做出贡献,但是贡献是
然后考虑竖条对答案的贡献,当前线段树覆盖的一堆线段会形成许多个分开的块,具象下来就是当前
像是这张图虽然有三个矩形但只形成了两个块,因此实际的竖条个数是当前块数(2)乘以2。
用线段树再维护一下当前区段是否充满就可以推出来一共有几个块,满了就不加,不满就块数相加。
考虑用扫描线解决。
用矩形框点太蛋疼了,把点变成框那么大的矩形再用扫描线处理。
每个矩形相当于可以提供它权值的贡献,把这些贡献在
虚树
怎么建树之前写过了,讲一讲题目思路吧。
虚树是一种优化手段,但不代表你得像鞋油那样先推朴素式子在优化。
甚至很多时候都是需要先用虚树把关键点抽象出来才能比较舒服的dp的。
像切断一个节点领导的子树有以下方法:
- 切断他所有儿子领导的子树
- 切断他自己的父亲与他的联系
跑虚树,预处理切断父亲的最小边后 dp 即可。
有个傻逼一看题就想到正解结果因为把题面看错了揣着正解看了半天不会做。
那个傻逼是我,我没看见它还要回去,一直想着怎么保证最小联通子树砍掉一遍路径能完整且最短。
事实上要是回去的话答案就是最小联通子树。
把最小联通子树的虚树跑出来。按dfn看相当于当前点和他在目标序列中的前驱后继求距离和。增加一个点就把他插入序列,更新答案,删除同理。增删且前后继+路径权值,直接对他使用平衡树+树剖生成300行恶臭代码,你就说过没过吧。
跑个虚树,考虑路径和:每条边将虚树分割为
按照贪心原则,树上最长链一定经过叶子即关键点,不用怕跑到lca上就不跑了。维护一个最长链和次长链,统计这个节点的儿子,有多个儿子就相加,没有就取最长。
最短路径可不是最短边,你得强制他经过两个叶子,维护到叶子的最短链和次短链,给一点细节让他必须经过叶子就行。
这道题我把题解发到学校oj上了,这个是链接。
还有一道世界树,自己推了半天发现和tj完全不沾边,有点小崩,先不写这个了。
主席树
本来想写的就是主席树小结,结果写到这剩三分钟放学,我呃呃。
教你如何实现可持久化,这种先建全树后修改的结构不会出现在接下来的任何一道题中,就因为这个坑了lz半天没明白主席树是想干啥。
主席树可以理解为对区间信息的前缀和,如果信息可以通过一定方法相减的话。(这已经揭示主席树除了前缀和查区间信息还可树套树改区间元素)。
因为可持久化线段树每多一个版本只需要新建
注意到模式串长度固定,预处理每位引导的hash然后对它建权值主席树,每次在第
这个题可以莫队。
这个题有一种经典的处理方法,后面也会用。
维护一个
查询
如何在树上查询路径中的第
考虑主席树的基本原理即前缀和(差分),既然序列上的静态问题可以用前缀和思想解决,那么树上的静态问题也是同理。
比如求路径和,那么可以预处理点到根节点的距离
同理地我们从根节点往下按dfn建权值主席树,那么点对
维护
inline void modify(int l,int r,int x,int pre,int &p){
p=++tot;
tree[p]=tree[pre];
++tree[p].val;
int mid=l+r>>1;
if(l==r)return;
if(x<=mid)modify(l,mid,x,ls(pre),ls(p));
else modify(mid+1,r,x,rs(pre),rs(p));
}
inline int query(int l,int r,int lx,int rx,int lcax,int fx,int k){
int mid=l+r>>1;
if(l==r)return l;
int v=tree[ls(rx)].val+tree[ls(lx)].val-tree[ls(lcax)].val-tree[ls(fx)].val;
if(v>=k)return query(l,mid,ls(lx),ls(rx),ls(lcax),ls(fx),k);
else return query(mid+1,r,rs(lx),rs(rx),rs(lcax),rs(fx),k-v);
}
跳左右儿子的过程直观不好想,感性理解吧。
这里提一嘴启发式合并。
这个东西听起来就特别潮,一搜全是紫题,其实就是一个猪鼻优化。
别名 dsu on tree,树上并查集(雾,相似地,维护散点所属集团的根节点,比对合并。
最开始每个点的首领是他自己,每次找到两个点时,找到较小 (siz) 的那个集团然后直接暴力把小树插在大树上,对就是再对小树跑一遍 dfs 重新汇总答案。看起来是
好现在看这道题。
如果没有 L 操作那么这道题就是上面的板题。现在考虑合并。既然建树的过程就是按树的结构造主席树,那每次合并就嗯和,连边,然后合并父亲,然后直接再建一次主席树。
inline void dfs(int u,int fa,int col){
vis[u]=col;
lcafa[0][u]=fa;
ST.modify(1,cnt,Val[u],ST.rt[fa],ST.rt[u]);
for(int i=1;i<=20;i++)lcafa[i][u]=lcafa[i-1][lcafa[i-1][u]];
dep[u]=dep[fa]+1;
siz[u]=1;
for(int i=h[u];i;i=edge[i].nxt){
int v=edge[i].v;
if(v==fa)continue;
dfs(v,u,col);
siz[u]+=siz[v];
}
}
...
if(opt[1]=='L'){
int fx=getf(x),fy=getf(y);
if(siz[fx]<siz[fy])swap(x,y),swap(fx,fy);
dfs(y,x,vis[x]);
siz[x]+=siz[y];
add(x,y);
add(y,x);
}
然后就好了。时间复杂度
好多好玩的题都在bzoj上,谷没有水不了通过
这里复习一下我学成史的数论知识。
发现区间中这个
eulerphi 中质因子是不能重复算的,相当于要求这个区间中有去重后质因子之积。
这不就是颜色序列那道题嘛!维护每个质因子上次出现的位置,主席树中的权值改成积之值,然后就可以过了。
最开始感觉像是区间求mex那种东西,发现不太好维护信息。
但是可以从暴力开始优化。设现在已经可以表示
现在新加一个数
要是
找不到合适的
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了