线段树维护最小生成树。两个结点合并就是把两个结点之间的两条横边加上后形成一个环,然后剪掉环上的最大值即可得到当前最小生成树。(不会证)
于是现在只需要考虑怎么找最大值。
首先这个环一定是如下构成:两边是左节点的最右竖边和右节点的最左竖边,然后中间是所有的上下两行横边。如下图红色部分。

那么发现维护每个节点的最右(左)竖边及其右(左)边的最大值即可。
然而,如果左边只剩下了一条竖边,而最大值又恰巧是这条边,那么删去这条边之后,左边就只剩下了横边,新节点的左右竖边就要用右节点的竖边更新。同时,我们还要找左节点的横边中的最大值,来更新新节点的最左竖边及其左边最大值。右边只剩一条边的情况同理。
总之,要分类讨论一些东西,草稿纸上画一画就很明白了。
整理一下,每个节点维护的是它所对应区间的最小生成树的信息,维护的东西如下:
l
/
r
:
l/r:
l/r:节点在序列中的左右端点位置。
l
_
v
a
l
/
r
_
v
a
l
:
l\_val/r\_val:
l_val/r_val:节点的最左/右竖边权值。
r
o
w
_
m
a
x
:
row\_max:
row_max:节点中横边的最大值。
c
n
t
:
cnt:
cnt:节点中竖边个数(特判上述特殊情况用)
l
_
m
a
x
/
r
_
m
a
x
:
l\_max/r\_max:
l_max/r_max:节点中最左/右竖边及其左/右边部分所有边权的最大值。
s
u
m
:
sum:
sum:节点维护的最小生成树的边权和。
注意这里我记录的行是
0
0
0和
1
1
1,输入的行是
1
1
1和
2
2
2,要记得减一…
#include<bits/stdc++.h>
#define lc (root<<1)
#define rc (root<<1|1)
#define mid (T[root].l+T[root].r>>1)
using namespace std;
const int maxn=6e4+10;
int n,m,row[2][maxn],clm[maxn];
int xa,ya,xb,yb,w,l,r;char op[10];
struct node{
int l,r,cnt,sum;
int l_val,r_val,l_max,r_max,row_max;
inline void init(int pos){
l=r=pos,cnt=1,row_max=0;
sum=l_max=r_max=l_val=r_val=clm[pos];
}
friend inline node operator+(const node &L,const node &R){
node ret;
ret.l=L.l,ret.r=R.r;
ret.row_max=max(max(row[0][L.r],row[1][L.r]),max(L.row_max,R.row_max));
int del=max(max(row[0][L.r],row[1][L.r]),max(L.r_max,R.l_max));
ret.sum=L.sum+R.sum+row[0][L.r]+row[1][L.r]-del,ret.cnt=L.cnt+R.cnt;
ret.l_val=L.l_val,ret.r_val=R.r_val;
ret.l_max=L.l_max,ret.r_max=R.r_max;
if(L.r_val==del){
ret.cnt--;
if(L.cnt==1){
ret.l_val=R.l_val;
ret.l_max=max(max(row[0][L.r],row[1][L.r]),max(L.row_max,R.l_max));
}
}
else if(R.l_val==del){
ret.cnt--;
if(R.cnt==1){
ret.r_val=L.r_val;
ret.r_max=max(max(row[0][L.r],row[1][L.r]),max(R.row_max,L.r_max));
}
}return ret;
}
}T[maxn<<2];
inline void build(int root,int l,int r){
if(l==r) return T[root].init(l);
int M=l+r>>1;
build(lc,l,M),build(rc,M+1,r);
T[root]=T[lc]+T[rc];
}
inline void update_clm(int root,int pos){
if(T[root].l==T[root].r)
return T[root].init(pos);
if(pos<=mid) update_clm(lc,pos);
else update_clm(rc,pos);
T[root]=T[lc]+T[rc];
}
//pos:(h,pos)->(h,pos+1)
inline void update_row(int root,int pos){
if(mid==pos){
T[root]=T[lc]+T[rc];
return;
}
if(pos<=mid) update_row(lc,pos);
else update_row(rc,pos);
T[root]=T[lc]+T[rc];
}
inline node query(int root,int l,int r){
if(l<=T[root].l&&T[root].r<=r) return T[root];
if(l> mid) return query(rc,l,r);
if(r<=mid) return query(lc,l,r);
return query(lc,l,mid)+query(rc,mid+1,r);
}
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x;
}
int main(){
// freopen("2708.in","r",stdin);
// freopen("2708.out","w",stdout);
n=read(),m=read();
//row[h][i]:(h,i)->(h,i+1)
for(int i=1;i< n;++i) row[0][i]=read();
for(int i=1;i< n;++i) row[1][i]=read();
for(int i=1;i<=n;++i) clm[i]=read();
build(1,1,n);
while(m--){
scanf("%s",op);
if(op[0]=='C'){
xa=read(),ya=read(),xb=read(),yb=read(),w=read();
if(ya==yb) clm[ya]=w,update_clm(1,ya);
else{
if(ya>yb) swap(ya,yb);
row[xa-1][ya]=w,update_row(1,ya);
}
}
if(op[0]=='Q') l=read(),r=read(),printf("%d\n",query(1,l,r).sum);
}
}
这篇博客介绍了如何利用线段树来维护最小生成树,重点在于处理两个节点合并时如何找到并剪掉环上的最大值。作者提到,环由左右节点的最右竖边和最左竖边以及中间的横边组成,并且讨论了特殊情况下节点信息的更新策略,包括节点的左右竖边、横边最大值等关键信息的维护。
297

被折叠的 条评论
为什么被折叠?



