寒假集训 数据结构
权值线段树
一个神奇的科技:通过简单重新编号可以使线段树只开两倍空间。对于一个区间 \([l,r]\),将节点编号设为 \((l+r)\operatorname{or}[l\ne r]\)。这样这个节点的标号范围为 \([2l,2r]\),实际 \(2mid+1\),左右儿子的标号范围为 \([2l,2mid]\),\([2mid+2,2r]\)。因此不会重复。
权值线段树:用线段树维护一个桶。
权值线段树按值域开空间,直接开四倍空间可能炸。这需要一个技巧:动态开点。刚开始只建出树根,当递归到一个没有的节点时再建出这个节点,空间复杂度降低到 \(O(m\log V)\)。
一道题:一棵树,点带权,一次操作修改一个点权或询问一条路径上某个权值的出现次数。
每个权值都开一个动态开点线段树,树上差分,维护每个结点到 \(1\) 的路径中该权值的个数。按 dfs 序建树,那么修改一个点,其子树会被影响,相当于区间 \(\pm1\)。
BZOJ3747
两个序列 \(w_i,f_i\),定义一个区间 \([l,r]\) 的价值为 \(\sum_{i=l}^r w_{f{i}}[f_i\text{在区间中仅出现过一次}]\)。求最大价值。
枚举右端点,记 \(last_i\) 为 \(f_i\) 上一次出现的位置,\(f_i\) 可贡献的区间为 \([last_i+1,i]\)。此时 \(last_i\) 的贡献 \([last_{last_{i}}+1,last_i]\) 由于 \(f_i\) 的存在不再产生贡献。线段树维护即可。
树套树
线段树套线段树:大的线段树维护第一维信息,每一个节点上再开一个小的线段树维护第二维信息。
树状数组套线段树:第一维换成树状数组。
一个序列,需要支持单点修改、区间 rank。
树状数组套权值线段树,第一维维护区间下标,第二维维护值域。查询时在线段树上二分,同时在 \(R\) 和 \(L-1\) 的 \(O(\log n)\) 棵线段树上跳。
标记永久化
标记永久化是懒惰标记之外维护区间修改的方式,不下传标记。
区间修改:对于完全包含的打标记,对于不完全包含的,直接算出这个节点的区间与修改区间的交集计算影响。
区间查询:对于标记,由于标记不下传,每个经过的节点都要算标记与查询区间的交集的贡献。
void add(int pos,int nl,int nr,int gl,int gr,T k){
if(gl<=nl&&nr<=gr){
tr[pos].tag+=k;
return;
}
tr[pos].v+=k*(min(nr,gr)-max(nl,gl)+1);
int mid=(nl+nr)>>1;
if(gl<=mid)add(pos<<1,nl,mid,gl,gr,k);
if(gr>mid)add(pos<<1|1,mid+1,nr,gl,gr,k);
}
T query(int pos,int nl,int nr,int gl,int gr){
if(gl<=nl&&nr<=gr)return tr[pos].v+tr[pos].tag*(nr-nl+1);
int mid=(nl+nr)>>1;
T ans=tr[pos].tag*(min(nr,gr)-max(nl,gl)+1);
if(gl<=mid)ans+=query(pos<<1,nl,mid,gl,gr);
if(gr>mid)ans+=query(pos<<1|1,mid+1,nr,gl,gr);
return ans;
}
李超线段树
在平面直角坐标系下维护两个操作:加入一条线段,询问与直线 \(x=k\) 的交点最靠上的线段。
每个线段树的节点维护一条线段,表示跨过 \([l,r]\) 的线段中与 \(x=mid\) 的交点中最靠上的。
设原线段 \(f_1\),新线段 \(f_2\),分类讨论修改:
1.\(f_1\) 整体比 \(f_2\) 高,直接退出;
2.\(f_1\) 整体比 \(f_2\) 高,替换后退出;
3.\(f_1,f_2\) 有交点,这个区间替换成 \(x=mid\) 处交点高的线段,舍去的那条线段往交点所在的子节点递归。
查询时计算线段树查询时经过的所有区间记录的线段对应的 \(y\) 值的最大值。
线段树合并
如果两棵树有一棵节点为空,则返回另一棵的节点,否则合并两个节点的信息,递归合并左右子树。复杂度取决于节点数量。
一个 \(n\) 个点的无向图,点带权,每次操作连边或询问一个点联通的点权第 \(k\) 小的点。
并查集维护连通性,每个连通块开一棵权值线段树维护重要度的第 \(k\) 小,连边就是线段树合并,查询在权值线段树上二分。
一棵树,每次在路径 \(x\rightsquigarrow y\) 上打种类为 \(z\) 的标记,标记不会被覆盖。问最后每个节点被打最多次哪一种标记。
每个节点开一棵权值线段树,树上差分维护,最后 \(dfs\) 将子树的线段树合并取最大值就是该节点的答案。
一棵完全二叉树,每个叶节点有一个权值,可以任意交换左右子树,问先序遍历数形成的权值序列的最小逆序对数。
交换左右儿子对其他节点没有影响,可以贪心地交换。每个节点维护一个权值线段树,用线段树合并求出交换前的逆序对和交换后的逆序对。
整体二分
有若干个可以二分的问题,可以利用整体二分合并这些二分的过程,一次二分解决所有问题。
静态区间 rank。
把原来数组的值当做修改与询问一起分治,函数 solve(int l1,int r1,int l2,int r2) 表示 \(l_2\sim r_2\) 的询问的答案和修改的数在 \([l_1,r_1]\) 之间。
对于每一个询问需要查询 \([l,r]\) 之间小于等于 \(mid\) 的数量 \(num\),如果小于 \(k\),说明这个询问的答案在 \([mid+1,r]\) 之间,否则在 \([l,mid]\) 之间。对于答案在 \([mid+1,r]\) 之间的询问还要减去 \(num\)。最后分成两部分调用 solve(l1,mid,l2,l2+cnt1-1),solve(mid+1,r1,l2+cnt1,r2) 递归。
关于统计 \(num\),在修改时如果 \(y\leq mid\) 就在 \(x\) 处加一,可以树状数组维护,递归之前清空。可以发现这样分治不会改变修改和询问的相对位置,因此是正确的。
边界条件:如果 \(l_1=r_1\) 就把当前询问的答案设为 \(l_1\) 后返回,如果 \(l_2>r_2\) 直接返回。
这题统计的是大于 \(mid\) 的个数,修改操作如果 \(c>mid\),相当于对 \([l,r]\) 都加一,把单点修改换成区间修改即可。
[[数据结构]]

浙公网安备 33010602011771号