CodeForces 527C. Glass Carving (SBT/线段树/std::set)

多次切割求最大矩形面积:

大致思路,对两条边分别找出被切割出的每一段长度的最大值,相乘就是答案。

有两种实现方法:

一:线段树

用1和0 表示每一条可被切割的线是否被切割,然后用线段树统计最长连续零的个数。

时间复杂度O(n* max( log2(w) , log2(h)))


二:SBT(Size Balanced Tree)

直接按顺序存下被切割之后的每一个小段的长度,每次切割的操作就是把其中的某个数分解成两个数。

比如开始长度为【11】,在3处切割:【3,8】。 然后在5处切割:【3,2,6】,在4处切割:【3,1,1,6】。

时间复杂度O(n * log2(n))


三:使用std::set

使用set存下被切割的横坐标,然后求该点的前驱和后继。就可以知道被切割的区间的长度。

然后另有数组存下每个区间长度出现的次数,每次操作维护这个次数。

具体解释见代码注释


方法比较:SBT:  202ms  4700KB     线段树:187ms 20400KB    std::set  171ms   7888KB

总的来说就是SBT占用空间小,但稍微慢一点(可能实现上还可以改进)

线段树占用空间大,但比SBT快一点(我试过了改成非递归线段树并没有比递归的快多少)

由于SBT的时间复杂度只与n有关,所以可以处理更大的w和h,通用性更强一些,但写起来也复杂一些。

相比之下,使用set由于不需要自己写一个树结构,编程复杂度比较低。


非递归线段树代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#define maxn 200001
using namespace std;
int L[maxn<<2][2];//从左开始连续零个数 
int R[maxn<<2][2];//从右 
int Max[maxn<<2][2];//区间最大连续零 
bool Pure[maxn<<2][2];//是否全零 
int M[2];
void PushUp(int rt,int k){//更新rt节点的四个数据 
	Pure[rt][k]=Pure[rt<<1][k]&&Pure[rt<<1|1][k]; 
	Max[rt][k]=max(R[rt<<1][k]+L[rt<<1|1][k],max(Max[rt<<1][k],Max[rt<<1|1][k]));
	L[rt][k]=Pure[rt<<1][k]?L[rt<<1][k]+L[rt<<1|1][k]:L[rt<<1][k];
	R[rt][k]=Pure[rt<<1|1][k]?R[rt<<1|1][k]+R[rt<<1][k]:R[rt<<1|1][k];
}
void Build(int n,int k){//建树,赋初值
	for(int i=0;i<M[k];++i) L[M[k]+i][k]=R[M[k]+i][k]=Max[M[k]+i][k]=Pure[M[k]+i][k]=i<n;
	for(int i=M[k]-1;i>0;--i) PushUp(i,k);
}
void Change(int X,int k){//切割,更新 
	int s=M[k]+X-1;
	Pure[s][k]=Max[s][k]=R[s][k]=L[s][k]=0;
	for(s>>=1;s;s>>=1) PushUp(s,k);
} 
int main(void)
{
	int w,h,n;
	while(cin>>w>>h>>n){
		//以下3行,找出非递归线段树的第一个数的位置。 
		M[0]=M[1]=1;
		while(M[0]<h-1) M[0]<<=1;
		while(M[1]<w-1) M[1]<<=1;
		//建树 
		Build(h-1,0);Build(w-1,1);
		
		for(int i=0;i<n;++i){
			//读取数据 
			char x;int v;
			scanf(" %c%d",&x,&v);
			//切割 
			x=='H'?Change(v,0):Change(v,1);
			//输出 
			printf("%I64d\n",(long long)(Max[1][0]+1)*(Max[1][1]+1));
		}
	}
return 0;
}


SBT 代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#define maxn 200007
using namespace std;
int L[maxn],R[maxn],Size[maxn];
int Max[maxn],Sum[maxn],Key[maxn];
int IP;
void Init(){//初始化 
	L[0]=R[0]=Size[0]=0;
	Max[0]=Sum[0]=Key[0]=0;
	IP=0;
}
void PushUp(int rt){//更新节点 
	Size[rt]=1+Size[L[rt]]+Size[R[rt]];
	Sum[rt]=Key[rt]+Sum[L[rt]]+Sum[R[rt]];
	Max[rt]=max(Key[rt],max(Max[L[rt]],Max[R[rt]]));
}
void zig(int &rt){//左旋 
	int t=R[rt];R[rt]=L[t];L[t]=rt;
	PushUp(rt);PushUp(t);rt=t;
}
void zag(int &rt){//右旋 
	int t=L[rt];L[rt]=R[t];R[t]=rt;
	PushUp(rt);PushUp(t);rt=t;
}
void maintain(int &rt){//平衡 
	if(Size[L[L[rt]]]>Size[R[rt]]) {zag(rt);maintain(R[rt]);maintain(rt);return;}
	if(Size[R[R[rt]]]>Size[L[rt]]) {zig(rt);maintain(L[rt]);maintain(rt);return;}
	if(Size[R[L[rt]]]>Size[R[rt]]) {zig(L[rt]);zag(rt);maintain(L[rt]);maintain(R[rt]);return;}
	if(Size[L[R[rt]]]>Size[L[rt]]) {zag(R[rt]);zig(rt);maintain(R[rt]);maintain(L[rt]);return;} 
}
void InsertLeft(int &rt,int X){//在rt这课树的最左端插入,Insert的辅助函数 
	if(rt) {
		InsertLeft(L[rt],X);PushUp(rt);maintain(rt);
	}
	else {
		rt=++IP;
		Sum[rt]=Max[rt]=Key[rt]=X;
		Size[rt]=1;L[rt]=R[rt]=0;
	}
}
void Insert(int &rt,int X){//在X处切割 
	if(X < Sum[L[rt]]) {Insert(L[rt],X);PushUp(rt);maintain(rt);return;}
	X-=Sum[L[rt]];
	if(X > Key[rt]){Insert(R[rt],X - Key[rt]);PushUp(rt);maintain(rt);return;}
	InsertLeft(R[rt],Key[rt]-X);
	Key[rt]=X;PushUp(rt);
}
int w,h,n;
int main(void)
{
	while(cin>>w>>h>>n){
		//初始化 
		Init();
		int H=0,V=0;  
		InsertLeft(H,h);InsertLeft(V,w);
		
		for(int i=0;i<n;++i){
			//读取 
			char x;int v;
			scanf(" %c%d",&x,&v);
			//切割 
			x=='H'?Insert(H,v):Insert(V,v);
			//输出 
			printf("%I64d\n",(long long)Max[H]*Max[V]);
		}
	}	
return 0;
}


std::set代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set> 
#define maxn 200001
using namespace std;
set<int>::iterator i,j;
set<int> H,V;
int Hn[maxn],Vn[maxn];
void Cut(set<int> &A,int *N,int v){//切割
	A.insert(v);i=j=A.find(v);
	--i,++j,--N[*j-*i];
	++N[v-*i],++N[*j-v];
}
int main(void)
{
	int w,h,n;
	while(cin>>w>>h>>n){
		memset(Hn,0,sizeof(Hn));H.clear();H.insert(h);H.insert(0);Hn[h]=1;
		memset(Vn,0,sizeof(Vn));V.clear();V.insert(w);V.insert(0);Vn[w]=1;
		int MaxH=h,MaxW=w; //MaxH表示H的最大值
		for(int i=0;i<n;++i){
			//读取数据 
			char x;int v;
			scanf(" %c%d",&x,&v);
			//切割 
			x=='H'?Cut(H,Hn,v):Cut(V,Vn,v);
			//输出 
			while(!Hn[MaxH]) --MaxH;//更新最大值,由于最大值一定不增,所以整个这个操作是O(h)的
			while(!Vn[MaxW]) --MaxW;//同上
			printf("%I64d\n",(long long)MaxH*MaxW);
		}
	}
return 0;
}





  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值