【BZOJ】3995: [SDOI2015]道路修建-线段树维护连通性

传送门:bzoj3995


题解

[ l , r ] [l,r] [l,r]表示第 l − r l-r lr列连通块的 M S T MST MST信息,考虑在线段树上合并两个区间:

连接 m i d → m i d + 1 mid\to mid+1 midmid+1上下的两条横线后,构成了一个以 [ l , m i d ] [l,mid] [l,mid]最右的一条竖线和 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]最左一条竖线构成的环,需要删去环上边权最大的边。

于是线段树每个节点维护:
mx \text{mx} mx:区间横线最大值
ls,rs \text{ls,rs} ls,rs:区间最左/右的竖线位置
lmx,rmx \text{lmx,rmx} lmx,rmx:区间最左/右的竖线再向左/右的横线最大值
sum \text{sum} sum:区间 M S T MST MST总边权

为方便合并,我底层维护的是 [ l , l + 1 ] [l,l+1] [l,l+1]区间而非单个节点,所以每次分治的时候是 [ l , r ] → [ l , m i d ] , [ m i d , r ] [l,r]\to [l,mid],[mid,r] [l,r][l,mid],[mid,r]

p.s.
这里感觉上每次维护 m x = ( l . m x , r . m x ) mx=(l.mx,r.mx) mx=(l.mx,r.mx)似乎有些问题(因为维护的是所有横线的最大权,而这个最大权可能已被删除),然而考虑什么情况下才需要调用这个 m x mx mx
当且仅当删了一条竖边,且这半部分不存在其它竖边时, l m x / r m x lmx/rmx lmx/rmx变成了这半部分的 m x mx mx和另外一半的 l m x / r m x lmx/rmx lmx/rmx max ⁡ \max max的值,由因为这半部分只存在一条竖边,所以其它横线都存在,所以 m x mx mx值是正确的。


代码

#include<bits/stdc++.h>
#define mid (l+r>>1)
#define lc k<<1
#define rc k<<1|1
#define lcc lc,l,mid
#define rcc rc,mid,r
typedef long long ll;
using namespace std;
const int N=120005;

int n,m;
int w[2][N],v[N];

char cp;
inline int init(){
    for(;;){
    	cp=getchar();
    	if(cp=='Q') return 1;if(cp=='C') return 0;
    }
}

struct node{
    int ls,rs,lmx,rmx,mx;ll sum;
    node operator +(node r){
    	node re;int mxx=max(rmx,r.lmx);
    	re.sum=sum+r.sum;re.mx=max(mx,r.mx);
    	re.ls=ls;re.rs=r.rs;
    	re.lmx=lmx;re.rmx=r.rmx;
    	if(mxx>max(v[rs],v[r.ls])) re.sum-=mxx;
    	else if(v[rs]>v[r.ls]){
    		re.sum-=v[rs];
    		if(ls==rs) re.ls=r.ls,re.lmx=max(mx,r.lmx);
    	}else{
    		re.sum-=v[r.ls];
    		if(r.ls==r.rs) re.rs=rs,re.rmx=max(rmx,r.mx);
    	}
    	return re;
    }
}t[N<<2];

inline node mg(int l,int r)
{
	node k;int x=max(v[l],v[r]),y=max(w[0][l],w[1][l]);
	k.mx=y;k.sum=(ll)v[l]+v[r]+w[0][l]+w[1][l]-max(x,y);
	if(x>y){
		if(v[l]==x) k.ls=k.rs=r,k.lmx=y,k.rmx=0;
		else k.ls=k.rs=l,k.rmx=y,k.lmx=0;
	}else k.ls=l,k.rs=r,k.lmx=k.rmx=0;
	return k;
}

void build(int k,int l,int r)
{
	if(r-1==l){t[k]=mg(l,r);return;}
	build(lcc);build(rcc);t[k]=t[lc]+t[rc];
}

void chg(int k,int l,int r,int L,int R)
{
	if(r-l==1){t[k]=mg(l,r);return;}
	if(L<=mid) chg(lcc,L,min(mid,R));
	if(R>=mid) chg(rcc,max(L,mid),R);
	t[k]=t[lc]+t[rc];
}

node ask(int k,int l,int r,int L,int R)
{
	if(L==l && r==R) return t[k];
	if(R<=mid) return ask(lcc,L,R);
	if(L>=mid) return ask(rcc,L,R);
	return (ask(lcc,L,mid)+ask(rcc,mid,R));
}

int main(){
	int i,j,x,y,z;
	scanf("%d%d",&n,&m);
	for(j=0;j<2;++j) 
	  for(i=1;i<n;++i) scanf("%d",&w[j][i]);
	for(i=1;i<=n;++i) scanf("%d",&v[i]);
	build(1,1,n);
	for(;m;--m){
		if(init()){
		    scanf("%d%d",&x,&y);
		    if(x==y) printf("%d\n",v[x]);else
		    printf("%lld\n",ask(1,1,n,x,y).sum);
		}else{
			scanf("%d%d%d%d%d",&x,&y,&i,&j,&z);if(y>j) swap(y,j);
			if(y^j) w[x-1][y]=z;else v[y]=z;chg(1,1,n,y,j);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值