• 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 }
  • 相关阅读:
    Linux下vsftpd的安装,Java上传文件实现。
    springboot整合shiro、redis及相关优化
    spring boot 配置文件
    spring boot 解决跨域访问
    spring-boot与spring-data-JPA的简单整合
    QQ空间里的“可能认识的人”深层思考????
    unix
    linux
    Linux中fork()函数详解
    word is too tall: try to use less letters, smaller font or bigger background 报错 java程序 验证码不显示
  • 原文地址:https://www.cnblogs.com/jiamian/p/11289137.html
Copyright © 2020-2023  润新知