SDOI2015 道路修建

这篇博客介绍了如何利用线段树来维护最小生成树,重点在于处理两个节点合并时如何找到并剪掉环上的最大值。作者提到,环由左右节点的最右竖边和最左竖边以及中间的横边组成,并且讨论了特殊情况下节点信息的更新策略,包括节点的左右竖边、横边最大值等关键信息的维护。
摘要由CSDN通过智能技术生成

传送门
参考博客

线段树维护最小生成树。两个结点合并就是把两个结点之间的两条横边加上后形成一个环,然后剪掉环上的最大值即可得到当前最小生成树。(不会证)
于是现在只需要考虑怎么找最大值。
首先这个环一定是如下构成:两边是左节点的最右竖边和右节点的最左竖边,然后中间是所有的上下两行横边。如下图红色部分。
在这里插入图片描述
那么发现维护每个节点的最右(左)竖边及其右(左)边的最大值即可。
然而,如果左边只剩下了一条竖边,而最大值又恰巧是这条边,那么删去这条边之后,左边就只剩下了横边,新节点的左右竖边就要用右节点的竖边更新。同时,我们还要找左节点的横边中的最大值,来更新新节点的最左竖边及其左边最大值。右边只剩一条边的情况同理。
总之,要分类讨论一些东西,草稿纸上画一画就很明白了。
整理一下,每个节点维护的是它所对应区间的最小生成树的信息,维护的东西如下:
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);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值