「SDOI 2015」道路修建

传送门


problem

某国有 2 n 2n 2n 个城市,这 2 n 2n 2n 个城市构成了一个 2 2 2 n n n 列的方格网。现在该国政府有一个旅游发展计划,这个计划需要选定 l l l r r r 两列 ( l ≤ r ) (l\le r) (lr),修建若干条专用道路,使得这两列之间(包括这两列)的所有 2 ( r − l + 1 ) 2(r-l+1) 2(rl+1) 个城市中每个城市可以只通过专用道路就可以到达这 2 ( r − l + 1 ) 2(r-l+1) 2(rl+1) 个城市中的任何一个城市。这种专用道路只能在同一行相邻两列的城市或者同一列的两个城市之间修建,且修建需要花费一定的费用。

由于该国政府决定尽量缩减开支,因此政府决定,选定 l l l r r r 后,只修建 2 ( r − l + 1 ) − 1 2(r-l+1)-1 2(rl+1)1 条专用道路,使得这些专用道路构成一个树结构。现在你需要帮助该国政府完成这个任务。具体地,该任务包含 m m m 个操作,每个操作的格式如下:

  • C x0 y0 x1 y1 w:由于重新对第 x 0 x_0 x0 行第 y 0 y_0 y0 列的城市和第 x 1 x_1 x1 行第 y 1 y_1 y1 列的城市之间的情况进行了考察,它们之间修建一条专用道路的花费变成了 w w w
  • Q l r:若政府选定的两列分别为 l l l r r r,询问政府的最小开支。

数据范围: 1 ≤ n , m ≤ 60000 1\le n, m\le60000 1n,m60000


solution

这道题的思维难度其实不算大,但是代码细节比较多。

线段树维护这个最小生成树,当合并线段树两个节点时会产生一个环,我们断掉环上最长边就是当前的答案。

于是我们只用找到这个环以及环上的最长边即可。

由于这个环的构成应该是:左右两条竖边,以及中间所有的横边。

那么,维护一下左儿子的最右竖边向右,右儿子的最左竖边向左的最大值,就可以快速算答案了。

有一个细节就是如果左儿子只有一条竖边而我们删的刚好就是这条边,我们要特殊处理一下。

反正,我们要分类讨论一些东西,建议在草稿纸上推一推。

以下是我们要维护的东西:

  • 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:节点维护的最小生成树的边权和。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

PS:可以看看巨神 wcr 的博客。


code

#include<bits/stdc++.h>
#define N 60005
using namespace std;
int n,m,row[2][N],clm[N];
struct Seg{
	int l,r,cnt,sum,l_val,r_val,l_max,r_max,row_max;
	void init(int pos){
		l=r=pos,cnt=1,row_max=0;
		l_val=r_val=l_max=r_max=sum=clm[pos];
	}
	friend Seg operator+(const Seg &L,const Seg &R){
		Seg now;
		now.l=L.l,now.r=R.r;
		now.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));
		now.sum=L.sum+R.sum+row[0][L.r]+row[1][L.r]-del,now.cnt=L.cnt+R.cnt;
		now.l_val=L.l_val,now.r_val=R.r_val;
		now.l_max=L.l_max,now.r_max=R.r_max;
		if(L.r_val==del){
			now.cnt--;
			if(L.cnt==1){
				now.l_val=R.l_val;
				now.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){
			now.cnt--;
			if(R.cnt==1){
				now.r_val=L.r_val;
				now.r_max=max(max(row[0][L.r],row[1][L.r]),max(R.row_max,L.r_max));
			}
		}
		return now;
	}
}T[N<<2];
#define mid ((l+r)>>1)
void build(int root,int l,int r){
	if(l==r)  return T[root].init(l);
	build(root<<1,l,mid),build(root<<1|1,mid+1,r);
	T[root]=T[root<<1]+T[root<<1|1];
}
void Modify_R(int root,int pos){
	int l=T[root].l,r=T[root].r;
	if(mid==pos)  {T[root]=T[root<<1]+T[root<<1|1];return;}
	if(pos<=mid)  Modify_R(root<<1,pos);
	else  Modify_R(root<<1|1,pos);
	T[root]=T[root<<1]+T[root<<1|1];
}
void Modify_C(int root,int pos){
	int l=T[root].l,r=T[root].r;
	if(l==r)  return T[root].init(l);
	if(pos<=mid)  Modify_C(root<<1,pos);
	else  Modify_C(root<<1|1,pos);
	T[root]=T[root<<1]+T[root<<1|1];
}
Seg Query(int root,int x,int y){
	int l=T[root].l,r=T[root].r;
	if(l>=x&&r<=y)  return T[root];
	if(y<=mid)  return Query(root<<1,x,y);
	if(x> mid)  return Query(root<<1|1,x,y);
	return Query(root<<1,x,y)+Query(root<<1|1,x,y);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;++i)  scanf("%d",&row[0][i]);
	for(int i=1;i<n;++i)  scanf("%d",&row[1][i]);
	for(int i=1;i<=n;++i)  scanf("%d",&clm[i]);
	build(1,1,n);
	int l,r,x1,y1,x2,y2,val;
	while(m--){
		char op=getchar();
		while(op!='Q'&&op!='C')  op=getchar();
		if(op=='C'){
			scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&val);
			if(y1==y2)  clm[y1]=val,Modify_C(1,y1);
			else{
				if(y1>y2)  swap(y1,y2);
				row[x1-1][y1]=val,Modify_R(1,y1);
			}
		}
		else  scanf("%d%d",&l,&r),printf("%d\n",Query(1,l,r).sum);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值