zoukankan      html  css  js  c++  java
  • CodeForces 527C. Glass Carving (SBT,线段树,set,最长连续0)

    原题地址: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 }
  • 相关阅读:
    wex5 实战 框架拓展之2 事件派发与data刷新
    wex5 实战 框架拓展之1 公共data组件(Data)
    wex5 实战 HeidiSQL 导入Excel数据
    wex5 实战 手指触屏插件 hammer的集成与优劣
    wex5 实战 登陆帐号更换与用户id一致性
    wex5 实战 用户点评与提交设计技巧
    wex5 实战 省市县三级联动与地址薄同步
    wex5 实战 wex5与js的组件关系与执行顺序(父子与先后)
    wex5 实战 单页模式下的多页面数据同步
    [BZOJ]4237: 稻草人
  • 原文地址:https://www.cnblogs.com/jiamian/p/11289137.html
Copyright © 2011-2022 走看看