(线段树) (h) poj3667 (区间合并)

poj3667 Hotel

如在阅读本文时遇到不懂的部分,请在评论区询问,或跳转 线段树总介绍

【题目大意】

有一个旅馆,有N个房间排成一排,现在有两种操作,第一是有X个顾客要入住连续的X个房间,
要求输出最小的左端点的位置,不能满足就输出0,第二是将以L开始,长度为X的连续房间清空。

【输入文件】

第一行两个数N,M,表示房间数和操作数

接下来M行,每行有两种情况:

1 X 表示操作1

2 L X 表示操作2

【输出文件】

对于每一个1操作,输出答案。

题即为求最靠左的连续区间并置满以及把一段区间置空。

那么我们的线段树正式进入了区间合并的部分

0表示没有牛,1表示住了牛

用pre(前驱)表示该区间内的前导零个数,last(后继,用suf比较好但当时写了last不想改了,一个意思),表示后导零个数,Maxilen表示区间内最大连续0的个数

,tag表示该区间内全部置为tag(初值为-1)

pushup注意特殊情况,若 左/右 区间的 前驱/后继 完全覆盖了 左/右 区间,要特殊处理

if(pre[LS]==mid-l+1)pre[rt]=pre[RS]+pre[LS];
else pre[rt]=pre[LS];
if(last[RS]==r-mid)last[rt]=last[RS]+last[LS];
else last[rt]=last[RS];
Maxilen[rt]=max (Maxilen[LS] ,Maxilen[RS] ,pre[RS]+last[LS] );//最大连续区间可能在 左边/右边/中间(所以为什么维护前驱后继)

pushdown

整段操作,赋0 or 赋1

 tag[LS]=tag[RS]=tag[rt];
 MaxiLen[LS]=pre[LS]=last[LS]=!tag[rt]?(mid-l+1):0;
 MaxiLen[RS]=pre[RS]=last[RS]=!tag[rt]?(r-mid):0;
 tag[rt]=-1; 

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<string>
using namespace std;
const int N=2e6+3;
int pre[N<<2],last[N<<2],MaxiLen[N<<2],tag[N<<2];
int n,m,L;
//tag -1 -> Nothing 0 ; 0/1 -> get to 0/1
#define LS (rt<<1)
#define RS (LS|1)
void pushup(int rt,int l,int r){
    MaxiLen[rt]=max(max(MaxiLen[LS],MaxiLen[RS]),pre[RS]+last[LS]);
    int mid=l+r>>1;
    if(pre[LS] == mid-l+1)pre[rt]=mid-l+1+pre[RS];
    else pre[rt]=pre[LS];
    if(last[RS] == r-mid)last[rt]=r-mid+last[LS];
    else last[rt]=last[RS];
}
void build(int rt,int l,int r){
    pre[rt]=last[rt]=MaxiLen[rt]=r-l+1;
    tag[rt]=-1;
    if(l==r)return;
    int mid=l+r>>1;
    build(LS,l,mid);
    build(RS,mid+1,r);
    //pushup(rt,l,r);
}
void pushdown(int rt,int l,int r){
    if(tag[rt] == -1)return;
    tag[LS]=tag[RS]=tag[rt];
    int mid=l+r>>1;
    MaxiLen[LS]=pre[LS]=last[LS]=!tag[rt]?(mid-l+1):0;
    MaxiLen[RS]=pre[RS]=last[RS]=!tag[rt]?(r-mid):0;
    tag[rt]=-1; return;
}
void update(int rt,int l,int r,int x,int y,int p){
    if(l>=x&&r<=y){
        tag[rt]=p; //直接覆盖
        MaxiLen[rt]=!p?(r-l+1):0;
        pre[rt]=last[rt]=!p?(r-l+1):0;
        return;
    }pushdown(rt,l,r);
    int mid=l+r>>1;
    if(x<=mid) update(LS,l,mid,x,y,p);
    if(y>mid) update(RS,mid+1,r,x,y,p);
    pushup(rt,l,r); return;
}
int query(int rt,int l,int r,int LEN){
    if(l==r)return l;  //一个点,因为一直优先往左,所以这个点一定是最靠左的
    int mid=l+r>>1;
    pushdown(rt,l,r);
    if(MaxiLen[LS]>=LEN) return query(LS,l,mid,LEN);
    if(last[LS]+pre[RS]>=LEN) return mid-last[LS]+1; //取得左儿子后继的开端
    if(MaxiLen[RS]>=LEN) return query(RS,mid+1,r,LEN);
    return 0; //if(MaxiLen[rt]<LEN)return 0;
}
int main(){
    scanf("%d%d",&n,&m);
        build(1,1,n);
        int ty,x,L;
        while(m--){
            scanf("%d",&ty);
            if(ty==1){
                scanf("%d",&x);
                L=query(1,1,n,x);
                printf("%d\n",L);
                if(L)update(1,1,n,L,L+x-1,1);
            }else {
                scanf("%d%d",&L,&x);
                update(1,1,n,L,L+x-1,0);
            }
        }

    return 0;
}

颜色是不是很好看qwq

End

原文地址:https://www.cnblogs.com/lsy263/p/11229039.html

时间: 2024-10-26 14:34:37

(线段树) (h) poj3667 (区间合并)的相关文章

hdu 3308 线段树单点更新 区间合并

http://acm.hdu.edu.cn/showproblem.php?pid=3308 学到两点: 1.以区间端点为开始/结束的最长......似乎在Dp也常用这种思想 2.分类的时候,明确标准逐层分类,思维格式: 条件一成立: { 条件二成立: { } else { } } else { 条件二成立: { } else { } } 上面的这种方式很清晰,如果直接想到那种情况iif(条件一 &条件二)就写,很容易出错而且把自己搞乱,或者情况不全,,,我就因为这WA了几次 3.WA了之后 ,

codeforces Good bye 2016 E 线段树维护dp区间合并

题目大意:给你一个字符串,范围为'0'~'9',定义一个ugly的串,即串中的子串不能有2016,但是一定要有2017,问,最少删除多少个字符,使得串中符合ugly串? 思路:定义dp(i, j),其中i=5,j=5,因为只需要删除2016当中其中一个即可,所以一共所需要删除的字符和需要的字符为20176,因此i和j只要5就够了. 然后转移就是dp(i,i) = 0, 如果说区间大小为1的话,那么如果是2017中的一个,那么就是dp(pos, pos+1) = 0, dp(pos,pos) =

POJ 2892 Tunnel Warfare(线段树单点更新区间合并)

Tunnel Warfare Time Limit: 1000MS   Memory Limit: 131072K Total Submissions: 7876   Accepted: 3259 Description During the War of Resistance Against Japan, tunnel warfare was carried out extensively in the vast areas of north China Plain. Generally sp

HDU 1540 Tunnel Warfare(线段树单点更新+区间合并)

Problem Description During the War of Resistance Against Japan, tunnel warfare was carried out extensively in the vast areas of north China Plain. Generally speaking, villages connected by tunnels lay in a line. Except the two at the ends, every vill

HDU 3308 LCIS (线段树&#183;单点更新&#183;区间合并)

题意  给你一个数组  有更新值和查询两种操作  对于每次查询  输出对应区间的最长连续递增子序列的长度 基础的线段树区间合并  线段树维护三个值  对应区间的LCIS长度(lcis)  对应区间以左端点为起点的LCIS长度(lle)  对应区间以右端点为终点的LCIS长度(lri)  然后用val存储数组对应位置的值  当val[mid + 1] > val[mid] 的时候就要进行区间合并操作了 #include <cstdio> #include <algorithm>

hdu 5316 Magician(2015多校第三场第1题)线段树单点更新+区间合并

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5316 题意:给你n个点,m个操作,每次操作有3个整数t,a,b,t表示操作类型,当t=1时讲a点的值改成b:当t=0时,查询区间a,b之间最大的子序列和,这个子序列中的相邻的元素的原来的下标奇偶性都不同. 思路:这道题难点就在查询,其余都是模板,而根据查询,你只要分别把下一个区间的奇偶最大的情况分别比较,合并到上一个区间这样可以构建一个每个节点存有区间中奇开头偶开头,奇结尾,偶结尾这些区间情况的树.

HDU 4553 线段树双关键字区间合并

用两个关键字记录,分别为屌丝时间和女神时间 若屌丝约,更新屌丝时间 若女神约,更新屌丝和女神时间 学习,则两个全部清空 #include "stdio.h" #include "string.h" struct Data { int l,r,x1,x2,l1,l2,r1,r2; }data[400010]; int Max(int a,int b) { if (a<b) return b;else return a; } void build(int l,in

线段树2 求区间最小值

线段树2 求区间最小值 从数组arr[0...n-1]中查找某个数组某个区间内的最小值,其中数组大小固定,但是数组中的元素的值可以随时更新. 数组[2, 5, 1, 4, 9, 3]可以构造如下的二叉树(背景为白色表示叶子节点,非叶子节点的值是其对应数组区间内的最小值,例如根节点表示数组区间arr[0...5]内的最小值是1): 线段树的四种操作: 1.线段树的创建 2.线段树的查询 3.线段树的更新单节点 4.线段树的更新区间 直接上完整代码吧 1 #include <bits/stdc++.

hdu2795(线段树单点更新&amp;区间最值)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2795 题意:有一个 h * w 的板子,要在上面贴 n 条 1 * x 的广告,在贴第 i 条广告时要尽量将其靠上贴,并输出其最上能贴在哪个位置: 思路:可以将每行剩余空间大小存储到一个数组中,那么对于当前 1 * x 的广告,只需找到所有剩余空间大于的 x 的行中位置最小的即可: 不过本题数据量为 2e5,直接暴力因该会 tle.可以用个线段树维护一下区间最大值,然后查询时对线段树二分即可: 代码