[IOI2014, 洛谷4560]Wall城墙

给定一个长度为 n 且初始值全为 0 的序列。你需要支持以下两种操作:

  • Add L,R,h:将序列 [L,R] 内所有值小于 h 的元素都赋为 h ,此时不改变高度大于 h 的元素值
  • Remove L,R,h :将序列 [L,R] 内所有值大于 h 的元素都赋为 h ,此时不改变高度小于 h 的元素值

你需要输出进行 k 次上述操作之后的序列。

这道题处看时没有一点想法, 脑海中只有“啊, 这怎么做?!”, 就先跳过去了.

后来做完 花神游历各国后 在一个晚上突然就有了想法: 维护区间的最大值, 最小值, 用来判断这个区间里的值是不是全都大于 h, 或小于 h. 感觉思路挺靠谱的, 就写了, 结果RE了...百思不得其解

先确认ql, qh的大小关系是否正确. 还是RE后, 我仔细想了想, 之前初学线段树时RE的原因, 是因为我无限递归了, 节点到了叶子节点时还是继续递归, 这样RE了很多次.

又观察 add 和 remove 函数里的 return 条件, 发现, 若是这个节点是叶子节点, 只要这个最大值最小值不满足条件还是会继续递归, 这就导致了RE.

我就在spread()函数前加了叶子节点手动return, 这样交之后, 出现了TLE, 这个时候我又继续想, 我是不是可以把不能修改的区间提前返回了呢? 发现确实可以这样, 在提交就AC了, 发现叶子节点判断有些多余, 删除后也AC了.

#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
const int MAXN = 3e6 + 600;
using namespace std;
int n, k;
struct Segment{
    int max, min, assTag;
} s[MAXN << 2 | 1];
int cmd, ql, qh, h;
inline void spread(int o){
    if(s[o].assTag != -1){// 因为 h >= 0, 所以这里选择 -1 做初始标记
        s[o << 1].max = s[o << 1 | 1].max = s[o].assTag;
        s[o << 1].min = s[o << 1 | 1].min = s[o].assTag;

        s[o << 1].assTag = s[o << 1 | 1].assTag = s[o].assTag;

        s[o].assTag = -1;
    }
}
inline void update(int o){
    s[o].max = max(s[o << 1].max, s[o << 1 | 1].max);
    s[o].min = min(s[o << 1].min, s[o << 1 | 1].min);
}
void build(int o, int lo, int hi){
    s[o].assTag = -1;// 这里注意把所有节点的assTag都初始为 -1, 这个无法在update函数中更新
    if(hi - lo < 2){
        s[o].max = s[o].min = 0;
        return;
    }
    int mi = (lo + hi) >> 1;
    build(o << 1, lo, mi);
    build(o << 1 | 1, mi, hi);

    update(o);
}
void add(int o, int lo, int hi){
    if(qh <= lo || hi <= ql){
        return;
    }
    if(ql <= lo && hi <= qh){
        if(s[o].max <= h){// 如果这个区间里的最大值小于等于h, 那么这个区间可以直接赋值
            s[o].max = s[o].min = h;
            s[o].assTag = h;
            return;
        }
        if(h <= s[o].min){// 如果这个区间的最小值都大于等于h, 那么这个区间不用改变
            return;
        }
    }
    // if(hi - lo < 2){// 手动return, 其实多余
    //    return;
    // }

    spread(o);
    int mi = (lo + hi) >> 1;
    add(o << 1, lo, mi);
    add(o << 1 | 1, mi, hi);

    update(o);
}
void remove(int o, int lo, int hi){
    if(qh <= lo || hi <= ql){
        return;
    }
    if(ql <= lo && hi <= qh){// 同理
        if(h <= s[o].min){
            s[o].max = s[o].min = h;
            s[o].assTag = h;
            return;
        }
        if(s[o].max <= h){
            return;
        }
    }

    spread(o);
    int mi = (lo + hi) >> 1;
    remove(o << 1, lo, mi);
    remove(o << 1 | 1, mi, hi);

    update(o);
}
void print(int o, int lo, int hi){
    if(hi - lo < 2){
        printf("%d\n", s[o].min);
        return;
    }

    spread(o);
    int mi = (lo + hi) >> 1;
    print(o << 1, lo, mi);
    print(o << 1 | 1, mi, hi);
}
int main(){
    scanf("%d%d", &n, &k);
    build(1, 0, n);
    for(int i = 0; i < k; ++i){
        scanf("%d%d%d%d", &cmd, &ql, &qh, &h);
        if(qh < ql){
            swap(ql, qh);
        }
        ++qh;
        if(cmd == 1){
            add(1, 0, n);
        }
        else{
            remove(1, 0, n);
        }
    }
    print(1, 0, n);
    return 0;
}

讲道理这道题的编码复杂度很低, 更重要的思维.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值