• 「POJ 2528」Mayor's posters


    题目链接 POJ 2528

    题意

    先给定一个(T),表示共有(T)组输入。

    对于每组输入,给定一个(n),表示下面有(n)([l, r])

    每对([l,r])的含义为:在给线段([l, r])染上一种新的颜色(会覆盖掉原来的颜色)。问最后线段上一共有几种不同的颜色?

    数据范围:(1 leq n leq 10^5, 1 leq l, r leq 10^7)。(注意:原题给定的(1 leq n leq 10000)的数据范围是错的)

    思路

    本题有好多种解法。关于线段树+离散化的解法有很多题解,这里就不写了(其实是我不会QwQ);

    这里介绍一下柯朵莉树和线段树动态开点的做法。

    柯朵莉树

    这题完全就是一柯朵莉树的板题。

    详细的柯朵莉树的介绍请参考oi-wiki,这里只讲与本题有关的内容。

    简单介绍一下柯朵莉树(又称老司机树,ODT):

    柯朵莉树是借助C++的set来实现大部分(O(log n))操作的。其原理就是,用一个struct存储线段的信息,其中struct长下面这样:

    struct Node {
        ll l, r;
        mutable ll v;
        Node(ll u, ll v = -1, ll w = 0): l(u), r(v), v(w) { }
        bool operator < (const Node &rhs) const {	// 按照l排序
            return this -> l < rhs.l;
        }
    };
    

    具体原理就是,用(l,r)表示区间的左端和右端,然后用(v)存区间的值。因为区间是依据(l)排序的,所以可以放心将(v)声明成mutable(与const相对,表示恒可修改)。

    set维护的柯朵莉树满足这样的性质:set中存储的点,其一定满足任意两个不同的点表示的区间不重叠。这样就会有一个问题,就是我们先给([1, 4])赋值为(2),然后又想给([3,5])赋值为(3)应该怎么办?

    所以就要用到split操作。我们先将([1,4])split([1,2],[3,5])两个区间,然后再进行操作。split函数如下:

    // 表示从pos这里分开成[l, pos - 1]和[pos, r],并返回[pos, r]的iterator
    set<Node>::iterator split(ll pos) {
        set<Node>::iterator it = s.lower_bound(pos);
        if (it != s.end() && it -> l == pos) return it;
        --it;
        ll L = it -> l, R = it -> r, V = it -> v;
    
        s.erase(it);
        s.insert(Node(L, pos - 1, V));
        return s.insert(Node(pos, R, V)).first;
    }
    

    (应该能直接看懂,就不加注释了)

    简单粗暴。

    最后,区间赋值。

    有了上面的介绍,就应该很简单了:

    void Assign(ll l, ll r, ll val = 0) {
        set<Node>::iterator itr = split(r + 1);
        set<Node>::iterator itl = split(l);
        s.erase(itl, itr);
        s.insert(Node(l, r, val));
    }
    

    注意必须先split(r + 1),然后再split(l),否则split(r + 1)后可能会导致itl变成野指针。

    最后,别忘了初始化。完整代码如下:

    // 875ms
    #include <cstdio>
    #include <algorithm>
    #include <iostream>
    #include <cstring>
    #include <set>
    #include <vector>
    #include <map>
    typedef long long ll;
    using namespace std;
    
    struct Node {
        ll l, r;
        mutable ll v;
        Node(ll u, ll v = -1, ll w = 0): l(u), r(v), v(w) { }
        bool operator < (const Node &rhs) const {
            return this -> l < rhs.l;
        }
    };
    
    set<Node> s;
    map<ll, ll> vis;
    ll T, n, x, y;
    
    set<Node>::iterator split(ll pos) {
        set<Node>::iterator it = s.lower_bound(pos);
        if (it != s.end() && it -> l == pos) return it;
        --it;
        ll L = it -> l, R = it -> r, V = it -> v;
    
        s.erase(it);
        s.insert(Node(L, pos - 1, V));
        return s.insert(Node(pos, R, V)).first;
    }
    
    void Assign(ll l, ll r, ll val = 0) {
        set<Node>::iterator itr = split(r + 1);
        set<Node>::iterator itl = split(l);
        s.erase(itl, itr);
        s.insert(Node(l, r, val));
    }
    
    inline void init() {
        s.clear();
        vis.clear();
        s.insert(Node(-0x3f3f3f3f3f3f3f3f, -1, 0));
        s.insert(Node(0, 0x3f3f3f3f3f3f3f3f, 0));
    }
    
    int main() {
        scanf("%lld", &T);
        while (T--) {
            init();
            scanf("%lld", &n);
            for (ll i = 1; i <= n; i++) {
                scanf("%lld%lld", &x, &y);
                if (x > y) swap(x, y);
                Assign(x, y, i);
            }
    
            for (set<Node>::iterator it = s.begin(); it != s.end(); it++) {
                vis[it -> v] = 1;
            }
            printf("%lld
    ", (ll)vis.size() - 1);
        }
        return 0;
    }
    

    线段树+动态开点

    传统的线段树我们查询rt左右子结点的时候,就直接rt << 1rt << 1 | 1了,是因为我们直接把数组当树状结构用了。但是本题的数据范围太大,而且会发现很多叶子结点根本用不到,所以可以考虑动态开点。

    动态开点要把原来直接用int存的线段树结点换成struct,因为我们还需要存左右子节点的位置,因为我们只有在用到这个结点的时候,才会去创建它。我们用专门的一个函数来表示创建新节点:

    int create() {
        cnt++;
        SegTree[cnt].l = 0;
        SegTree[cnt].r = 0;
        SegTree[cnt].val = 0;
        return (cnt);
    }
    

    这里赋值为(0)是表示初始化,因为有多组输入。

    其他与传统线段树不同的地方,基本就只有在访问子节点的时候需要先判断一下有没有子节点,没有的话需要先创建子节点。

    所以就直接上完整代码了:

    // 407ms
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <set>
    
    typedef long long ll;
    using namespace std;
    
    const int maxn = 2e5 + 5;
    
    int cnt, T, n;
    // 因为set会自动将重复的点合并,所以就直接用set存了
    set<int> res;
    struct Node {
        int val;
        ll l, r;
    };
    
    Node SegTree[maxn << 2];
    
    int create() {
        cnt++;
        SegTree[cnt].l = 0;
        SegTree[cnt].r = 0;
        SegTree[cnt].val = 0;
        return (cnt);
    }
    
    // 本题并没有用到lazy,所以pushdown其实大概相当于pushup
    // 只不过因为题目特点,这个是将点的值向下放
    void pushdown(int rt) {
        if (SegTree[rt].val) {
            if (!SegTree[rt].l) SegTree[rt].l = create();
            SegTree[SegTree[rt].l].val = SegTree[rt].val;
            if (!SegTree[rt].r) SegTree[rt].r = create();
            SegTree[SegTree[rt].r].val = SegTree[rt].val;
        }
        SegTree[rt].val = 0;
    }
    
    void modify(int rt, ll l, ll r, ll L, ll R, int val) {
        if (L <= l && r <= R) {
            SegTree[rt].val = val;
            return ;
        }
        ll mid = (l + r) >> 1;
        pushdown(rt);
        if (L <= mid) {
            if (!SegTree[rt].l) SegTree[rt].l = create();
            modify(SegTree[rt].l, l, mid, L, R, val);
        }
        if (R > mid) {
            if (!SegTree[rt].r) SegTree[rt].r = create();
            modify(SegTree[rt].r, mid + 1, r, L, R, val);
        }
    }
    
    void query(int rt) {
        if (SegTree[rt].val) {
            res.insert(SegTree[rt].val);
            return ;
        }
        if (SegTree[rt].l) query(SegTree[rt].l);
        if (SegTree[rt].r) query(SegTree[rt].r);
    }
    
    void init() {
        cnt = 0;
        res.clear();
        SegTree[0].l = 0; SegTree[0].r = 0; SegTree[0].val = 0;
    }
    
    int main() {
        scanf("%d", &T);
        while (T--) {
            init();
            scanf("%d", &n);
            for (int i = 1; i <= n; i++) {
                ll x, y;
                scanf("%lld%lld", &x, &y);
                modify(0, 1, 10000000, x, y, i);
            }
            query(0);
            printf("%lld
    ", (long long)res.size());
        }
        return 0;
    }
    

    虽然都是(O(n log n))的复杂度,但是实测动态开点速度基本上是柯朵莉树的两倍。可能是因为本来STL的常数就大,而且柯朵莉树的操作还比较多。数据离散化因为本蒟蒻不会写,所以并不知道具体速度QAQ

  • 相关阅读:
    OA权限管理的实现(下)
    Eclipse及其插件介绍和下载(转)
    [转载]在Redhat Linux AS 4下实现软件RAID
    RAID磁盘阵列术语详解(转)
    [转载]关于"编译器失败,错误代码为128。"的解决方案
    Android的SurfaceView使用
    剖析 SurfaceView Callback以及SurfaceHolder
    android drawable 应用
    Android layout xml总结
    listView控件,adapter,以及其他控件的组合
  • 原文地址:https://www.cnblogs.com/icysky/p/13772003.html
Copyright © 2020-2023  润新知