原题地址:http://codeforces.com/problemset/problem/527/C
Examples
input
4 3 4 H 2 V 2 V 3 V 1
output
8 4 4 2
input
7 6 5 H 4 V 3 V 5 H 2 V 1
output
28 16 12 6 4
题意是给定一个矩形,不停地纵向或横向切割,问每次切割后,最大的矩形面积是多少。
最大矩形面积=最长的长*最宽的宽
这题,长宽都是10^5,所以,用0 1序列表示每个点是否被切割,然后,
最长的长就是长的最长连续0的数量+1
最长的宽就是宽的最长连续0的数量+1
于是用线段树维护最长连续零
问题转换成:
目标信息:区间最长连续零的个数
点信息:0 或 1
由于目标信息不符合区间加法,所以要扩充目标信息。
转换后的线段树结构:
区间信息:从左,右开始的最长连续零,本区间是否全零,本区间最长连续零。
点信息:0 或 1
然后还是那2个问题:
1.区间加法:
这里,一个区间的最长连续零,需要考虑3部分:
-(1):左子区间最长连续零
-(2):右子区间最长连续零
-(3):左右子区间拼起来,而在中间生成的连续零(可能长于两个子区间的最长连续零)
而中间拼起来的部分长度,其实是左区间从右开始的最长连续零+右区间从左开始的最长连续零。
所以每个节点需要多两个量,来存从左右开始的最长连续零。
然而,左开始的最长连续零分两种情况,
--(1):左区间不是全零,那么等于左区间的左最长连续零
--(2):左区间全零,那么等于左区间0的个数加上右区间的左最长连续零
于是,需要知道左区间是否全零,于是再多加一个变量。
最终,通过维护4个值,达到了维护区间最长连续零的效果。
2.点信息->区间信息 :
如果是0,那么 最长连续零=左最长连续零=右最长连续零=1 ,全零=true。
如果是1,那么 最长连续零=左最长连续零=右最长连续零=0, 全零=false。
至于修改和查询,有了区间加法之后,机械地写一下就好了。
由于这里其实只有对整个区间的查询,所以查询函数是不用写的,直接找根的统计信息就行了。
递归线段树:
1 #include <stdio.h> 2 #include <string.h> 3 #include <iostream> 4 #include <string> 5 #include <math.h> 6 #include <algorithm> 7 #include <queue> 8 #include <set> 9 #include <math.h> 10 const int INF=0x3f3f3f3f; 11 typedef long long LL; 12 const int maxn=2e5+10; 13 using namespace std; 14 15 16 struct SegTree{ 17 int ls;//左最长连续零 18 int rs;//右最长连续零 19 int max0;//最长连续零 20 bool is_all0;//是否全为零 21 }tree[2][maxn<<2]; 22 23 void PushUp(int rt,int flag)//区间加法 24 { 25 SegTree &root=tree[flag][rt]; 26 SegTree &lc=tree[flag][rt<<1]; 27 SegTree &rc=tree[flag][rt<<1|1]; 28 root.ls=lc.ls+(lc.is_all0?rc.ls:0); 29 root.rs=rc.rs+(rc.is_all0?lc.rs:0); 30 root.max0=max(lc.rs+rc.ls,max(lc.max0,rc.max0)); 31 root.is_all0=lc.is_all0&&rc.is_all0; 32 } 33 34 void Build(int l,int r,int rt,int flag)//建树 35 { 36 if(l==r) 37 { 38 tree[flag][rt].ls=tree[flag][rt].rs=tree[flag][rt].max0=1; 39 tree[flag][rt].is_all0=true; 40 return ; 41 } 42 int m=(l+r)>>1; 43 Build(l,m,rt<<1,flag); 44 Build(m+1,r,rt<<1|1,flag); 45 PushUp(rt,flag); 46 } 47 48 void Update(int l,int r,int rt,int pos,int flag)//更新 49 { 50 if(l==r) 51 { 52 tree[flag][rt].ls=tree[flag][rt].rs=tree[flag][rt].max0=0; 53 tree[flag][rt].is_all0=false; 54 return ; 55 } 56 int m=(l+r)>>1; 57 if(pos<=m) 58 Update(l,m,rt<<1,pos,flag); 59 else 60 Update(m+1,r,rt<<1|1,pos,flag); 61 PushUp(rt,flag); 62 } 63 64 int main() 65 { 66 int W,H,q,t; 67 char c[5]; 68 scanf("%d %d %d",&W,&H,&q); 69 Build(1,W-1,1,0); 70 Build(1,H-1,1,1); 71 while(q--) 72 { 73 scanf("%s %d",&c,&t); 74 if(c[0]=='V') 75 Update(1,W-1,1,t,0); 76 else if(c[0]=='H') 77 Update(1,H-1,1,t,1); 78 printf("%lld ",LL(tree[0][1].max0+1)*(tree[1][1].max0+1));//相乘可能整数溢出,记得类型装换 79 } 80 return 0; 81 }
以下是大神写的,原地址:https://blog.csdn.net/zearot/article/details/44759437
多次切割求最大矩形面积:
大致思路,对两条边分别找出被切割出的每一段长度的最大值,相乘就是答案。
有三种实现方法:
一:线段树
用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由于不需要自己写一个树结构,编程复杂度比较低。
非递归线段树:
1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #define maxn 200001 5 using namespace std; 6 int L[maxn<<2][2];//从左开始连续零个数 7 int R[maxn<<2][2];//从右 8 int Max[maxn<<2][2];//区间最大连续零 9 bool Pure[maxn<<2][2];//是否全零 10 int M[2]; 11 void PushUp(int rt,int k){//更新rt节点的四个数据 12 Pure[rt][k]=Pure[rt<<1][k]&&Pure[rt<<1|1][k]; 13 Max[rt][k]=max(R[rt<<1][k]+L[rt<<1|1][k],max(Max[rt<<1][k],Max[rt<<1|1][k])); 14 L[rt][k]=Pure[rt<<1][k]?L[rt<<1][k]+L[rt<<1|1][k]:L[rt<<1][k]; 15 R[rt][k]=Pure[rt<<1|1][k]?R[rt<<1|1][k]+R[rt<<1][k]:R[rt<<1|1][k]; 16 } 17 void Build(int n,int k){//建树,赋初值 18 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; 19 for(int i=M[k]-1;i>0;--i) PushUp(i,k); 20 } 21 void Change(int X,int k){//切割,更新 22 int s=M[k]+X-1; 23 Pure[s][k]=Max[s][k]=R[s][k]=L[s][k]=0; 24 for(s>>=1;s;s>>=1) PushUp(s,k); 25 } 26 int main(void) 27 { 28 int w,h,n; 29 while(cin>>w>>h>>n){ 30 //以下3行,找出非递归线段树的第一个数的位置。 31 M[0]=M[1]=1; 32 while(M[0]<h-1) M[0]<<=1; 33 while(M[1]<w-1) M[1]<<=1; 34 //建树 35 Build(h-1,0);Build(w-1,1); 36 37 for(int i=0;i<n;++i){ 38 //读取数据 39 char x;int v; 40 scanf(" %c%d",&x,&v); 41 //切割 42 x=='H'?Change(v,0):Change(v,1); 43 //输出 44 printf("%I64d ",(long long)(Max[1][0]+1)*(Max[1][1]+1)); 45 } 46 } 47 return 0; 48 } 49
SBT:
1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #define maxn 200007 5 using namespace std; 6 int L[maxn],R[maxn],Size[maxn]; 7 int Max[maxn],Sum[maxn],Key[maxn]; 8 int IP; 9 void Init(){//初始化 10 L[0]=R[0]=Size[0]=0; 11 Max[0]=Sum[0]=Key[0]=0; 12 IP=0; 13 } 14 void PushUp(int rt){//更新节点 15 Size[rt]=1+Size[L[rt]]+Size[R[rt]]; 16 Sum[rt]=Key[rt]+Sum[L[rt]]+Sum[R[rt]]; 17 Max[rt]=max(Key[rt],max(Max[L[rt]],Max[R[rt]])); 18 } 19 void zig(int &rt){//左旋 20 int t=R[rt];R[rt]=L[t];L[t]=rt; 21 PushUp(rt);PushUp(t);rt=t; 22 } 23 void zag(int &rt){//右旋 24 int t=L[rt];L[rt]=R[t];R[t]=rt; 25 PushUp(rt);PushUp(t);rt=t; 26 } 27 void maintain(int &rt){//平衡 28 if(Size[L[L[rt]]]>Size[R[rt]]) {zag(rt);maintain(R[rt]);maintain(rt);return;} 29 if(Size[R[R[rt]]]>Size[L[rt]]) {zig(rt);maintain(L[rt]);maintain(rt);return;} 30 if(Size[R[L[rt]]]>Size[R[rt]]) {zig(L[rt]);zag(rt);maintain(L[rt]);maintain(R[rt]);return;} 31 if(Size[L[R[rt]]]>Size[L[rt]]) {zag(R[rt]);zig(rt);maintain(R[rt]);maintain(L[rt]);return;} 32 } 33 void InsertLeft(int &rt,int X){//在rt这课树的最左端插入,Insert的辅助函数 34 if(rt) { 35 InsertLeft(L[rt],X);PushUp(rt);maintain(rt); 36 } 37 else { 38 rt=++IP; 39 Sum[rt]=Max[rt]=Key[rt]=X; 40 Size[rt]=1;L[rt]=R[rt]=0; 41 } 42 } 43 void Insert(int &rt,int X){//在X处切割 44 if(X < Sum[L[rt]]) {Insert(L[rt],X);PushUp(rt);maintain(rt);return;} 45 X-=Sum[L[rt]]; 46 if(X > Key[rt]){Insert(R[rt],X - Key[rt]);PushUp(rt);maintain(rt);return;} 47 InsertLeft(R[rt],Key[rt]-X); 48 Key[rt]=X;PushUp(rt); 49 } 50 int w,h,n; 51 int main(void) 52 { 53 while(cin>>w>>h>>n){ 54 //初始化 55 Init(); 56 int H=0,V=0; 57 InsertLeft(H,h);InsertLeft(V,w); 58 59 for(int i=0;i<n;++i){ 60 //读取 61 char x;int v; 62 scanf(" %c%d",&x,&v); 63 //切割 64 x=='H'?Insert(H,v):Insert(V,v); 65 //输出 66 printf("%I64d ",(long long)Max[H]*Max[V]); 67 } 68 } 69 return 0; 70 }
std::set代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <set> 5 #define maxn 200001 6 using namespace std; 7 set<int>::iterator i,j; 8 set<int> H,V; 9 int Hn[maxn],Vn[maxn]; 10 void Cut(set<int> &A,int *N,int v){//切割 11 A.insert(v);i=j=A.find(v); 12 --i,++j,--N[*j-*i]; 13 ++N[v-*i],++N[*j-v]; 14 } 15 int main(void) 16 { 17 int w,h,n; 18 while(cin>>w>>h>>n){ 19 memset(Hn,0,sizeof(Hn));H.clear();H.insert(h);H.insert(0);Hn[h]=1; 20 memset(Vn,0,sizeof(Vn));V.clear();V.insert(w);V.insert(0);Vn[w]=1; 21 int MaxH=h,MaxW=w; //MaxH表示H的最大值 22 for(int i=0;i<n;++i){ 23 //读取数据 24 char x;int v; 25 scanf(" %c%d",&x,&v); 26 //切割 27 x=='H'?Cut(H,Hn,v):Cut(V,Vn,v); 28 //输出 29 while(!Hn[MaxH]) --MaxH;//更新最大值,由于最大值一定不增,所以整个这个操作是O(h)的 30 while(!Vn[MaxW]) --MaxW;//同上 31 printf("%I64d ",(long long)MaxH*MaxW); 32 } 33 } 34 return 0; 35 }