标签: 扫描线

  • CFGYM101982F (2018 ICPC Pacific Northwest Regional Contest – Rectangles)

    题意:给定n(1e5)个矩形,边与坐标轴平行,四个点的坐标都是整数(1e9级别),问被矩形覆盖了奇数次的面积和。

    首先把所有的矩形拆成上边和下边两条边,这样我们就得到了若干条与x轴平行的线段。然后我们把这些线段按照y坐标从小到大排个序。

    现在我们维护一棵线段树,下标表示的是(离散化后的)整个横轴的覆盖情况。以及为了方便起见,整个线段树上所表示的区间都是左闭右开的(一条线段所能表示的实际长度是右端点减左端点)。

    现在我们按照y坐标从小到大枚举每一条横线,对于一条横线,我们把它在线段树上所覆盖的的区域异或上1,同时面积取反(即用线段长度减掉区域内被标记过的线段长度和,此操作即为异或),由于我们存储的都是左闭右开区间,所以直接用右端点对应的数减掉左端点对应的数即可。

    最后每插入一条线段,我们就查一下现在线段树上被覆盖的区间长度,然后乘上下一条线段的高度减掉当前线段的高度(此方法是可以正常处理有相同高度的线段的,因为他们高度相同,所以算出来都是0)。

    #include <algorithm>
    #include <iostream>
    #include <vector>
    
    using int_t = long long int;
    
    using std::cin;
    using std::cout;
    using std::endl;
    
    struct Segment {
        int_t left, right, height;
    };
    struct Node {
        Node *left = nullptr, *right = nullptr;
        int_t begin, end;
        bool flag = false;
        int_t sum = 0;
        const std::vector<int_t>& vals;
        Node(int_t begin, int_t end, const std::vector<int_t>& vals)
            : begin(begin), end(end), vals(vals) {}
        void swap() {
            flag ^= 1;
            sum = vals[end] - vals[begin] - sum;
        }
        void pushdown() {
            if (flag) {
                left->swap();
                right->swap();
                flag = 0;
            }
        }
        void maintain() { sum = left->sum + right->sum; }
        void insert(int_t begin, int_t end) {  //插入一条线段
    #ifdef DEBUG
            cout << "insert " << begin << "," << end << " to " << this->begin << ","
                 << this->end << endl;
    #endif
            if (this->begin >= begin && this->end <= end) {
                this->swap();
                return;
            }
            if (this->begin >= end || this->end <= begin) {
                return;
            }
            int_t mid0 = (this->begin + this->end) / 2;
            pushdown();
            left->insert(begin, end);
            right->insert(begin, end);
            maintain();
        }
    };
    Node* build(int_t begin, int_t end, const std::vector<int_t>& vals) {
        Node* node = new Node(begin, end, vals);
        if (begin + 1 != end) {
            int_t mid = (begin + end) / 2;
            node->left = build(begin, mid, vals);
            node->right = build(mid, end, vals);
        }
        return node;
    }
    int main() {
        std::ios::sync_with_stdio(false);
        std::vector<Segment> segs;
        std::vector<int_t> nums;
        int_t n;
        cin >> n;
        for (int_t i = 1; i <= n; i++) {
            int_t x1, y1, x2, y2;
            cin >> x1 >> y1 >> x2 >> y2;
            segs.push_back(Segment{x1, x2, y1});
            segs.push_back(Segment{x1, x2, y2});
            nums.push_back(x1);
            nums.push_back(x2);
        }
        std::sort(nums.begin(), nums.end());
        nums.resize(std::unique(nums.begin(), nums.end()) - nums.begin());
    
        std::sort(segs.begin(), segs.end(), [](const Segment& a, const Segment& b) {
            return a.height < b.height;
        });
        int_t result = 0;
        Node* root = build(0, nums.size() - 1, nums);
        const auto rank = [&](int_t x) {
            return std::lower_bound(nums.begin(), nums.end(), x) - nums.begin();
        };
    #ifdef DEBUG
        for (const auto& s : segs) {
            cout << "seg " << s.left << " " << s.right << " " << s.height << endl;
        }
    #endif
        for (int_t i = 0; i < segs.size() - 1; i++) {
            //先插入后统计结果
            const auto& curr = segs[i];
            root->insert(rank(curr.left), rank(curr.right));
            result += (segs[i + 1].height - curr.height) * root->sum;
        }
        cout << result << endl;
        return 0;
    }
    
  • YT2sOJ04 give you a tree

    zzs上辈子就会做的题我现在才会做..

    一个区间能形成一个联通块,当且仅当这个区间内的边有 区间长度-1 条。

    考虑扫描线。

    每个点存下终点编号比它小的边。

    然后从1开始扫,设当前扫到的点为vtx,维护一棵线段树,线段树下标为x的点的值减掉x后表示的是区间[x,vtx]内存在的边数。

    每次扫到先vtx后,首先把vtx的值设置成vtx,然后枚举vtx一条终点编号比vtx小的出边v,显然v可以给左端点区间在[1,v]内的区间贡献一条边(让以这些地方为左端点,vtx为右端点的区间包括的边数多了1)。

    考虑一个左端点什么时候会成为合法的左端点。

    设这个左端点是x,这个左端点在线段树上的值为val,当且仅当$val-x=vtx-x$(根据上面的定义,线段树下标为x的点的值减掉x后表示的是区间[x,vtx]内存在的边数),即$val=vtx$时,这个左端点会成为一个合法的左端点。

    所以只需要维护全局最大值出现的次数即可。

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <vector>
    #include <inttypes.h>
    #define debug(x) std::cout << #x << " = " << x << std::endl;
    
    typedef int int_t;
    
    using std::cin;
    using std::endl;
    using std::cout;
    const int_t LARGE = 3e5;
    struct Node {
        Node* left, *right;
        int_t begin, end;
        int_t mark;
        int_t count;
        int_t max;
        void maintain() {
            if (begin != end) {
                max = std::max(left->max, right->max);
                count = 0;
                if (left->max == max)
                    count += left->count;
                if (right->max == max)
                    count += right->count;
            }
        }
        void add(int_t x) {
            max += x;
            mark += x;
        }
        void pushDown() {
            if (begin != end) {
                left->add(mark);
                right->add(mark);
                mark = 0;
            }
        }
        Node(int_t begin, int_t end) {
            this->begin = begin;
            this->end = end;
            max = mark = count = 0;
            left = right = nullptr;
        }
        void add(int_t begin, int_t end, int_t val) {
            if (end < this->begin || begin > this->end) {
                return;
            }
            if (this->begin >= begin && this->end <= end) {
                this->add(val);
                return;
            }
            pushDown();
            left->add(begin, end, val);
            right->add(begin, end, val);
            maintain();
        }
        void setPos(int_t pos, int_t val) {
            if (begin == end) {
                this->max = pos;
                this->count = 1;
                return;
            }
            int_t mid = (begin + end) / 2;
            pushDown();
            if (pos <= mid)
                left->setPos(pos, val);
            else
                right->setPos(pos, val);
            maintain();
        }
        static Node* build(int_t begin, int_t end) {
            Node* node = new Node(begin, end);
            if (begin != end) {
                int_t mid = (begin + end) / 2;
                node->left = Node::build(begin, mid);
                node->right = Node::build(mid + 1, end);
                node->maintain();
            }
            return node;
        }
    };
    std::vector<int_t> graph[LARGE + 1];
    int_t n;
    int main() {
        scanf("%d", &n);
        for (int_t i = 1; i <= n - 1; i++) {
            int_t v1, v2;
            scanf("%d%d", &v1, &v2);
            if (v1 < v2)
                std::swap(v1, v2);
            graph[v1].push_back(v2);
        }
        int64_t result = 0;
        Node* root = Node::build(1, n);
        for (int_t i = 1; i <= n; i++) {
            root->setPos(i, i);
            for (int_t to : graph[i]) root->add(1, to, 1);
            result += root->count;
    #ifdef DEBUG
            cout << "got " << root->count << " at vtx " << i << endl;
    #endif
        }
        printf("%lld\n", result);
        return 0;
    }

     

  • Luogu4113 HEOI2012 采花

    题意:

    求一段区间内,出现次数至少为2的数的个数。

    可以考虑转化成像HH的项链一样的模型。

    扫描线+树状数组。

    考虑左端弹出一个数会造成什么影响,设弹出的数为x,那么一直到x的下一次出现位置的这个区间不受任何影响(因为x只出现了0次或一次,不计入答案),然而对于x的下一次出现位置,到下下一次出现位置之间的答案会减一,因为x本来在这些地方出现了两次,但是我们刚刚把他删了,所以就剩下一次了,对于更靠右的地方,答案不变,因为那些地方x至少出现了三次。

    #include <algorithm>
    #include <cstdio>
    #include <iostream>
    
    using int_t = int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 2e6 + 10;
    
    struct Operation {
        int_t left, right;
        int_t result;
        int_t id;
        bool operator<(const Operation& x) const { return left < x.left; }
    } opts[LARGE + 1];
    int_t n, c, m;
    int_t arr[LARGE + 1];
    int_t next[LARGE + 1];
    int_t prev[LARGE + 1];
    //颜色k最后一次出现的位置
    int_t last[LARGE + 1];
    int_t colors[LARGE + 1];
    inline int_t lowbit(int_t x) { return x & (-x); }
    void add(int_t pos, int_t val) {
        while (pos <= n) {
            arr[pos] += val;
            pos += lowbit(pos);
        }
    }
    int_t query(int_t pos) {
        int_t result = 0;
        while (pos >= 1) {
            result += arr[pos];
            pos -= lowbit(pos);
        }
        return result;
    }
    int main() {
        scanf("%d%d%d", &n, &c, &m);
        for (int_t i = 1; i <= n; i++) scanf("%d", &colors[i]);
        for (int_t i = 1; i <= m; i++) {
            auto& thiz = opts[i];
            scanf("%d%d", &thiz.left, &thiz.right);
            thiz.id = i;
        }
        std::sort(opts + 1, opts + 1 + m);
        for (int_t i = 1; i <= n; i++) {
            int_t color = colors[i];
            if (last[color] == 0) {
                last[color] = i;
                prev[color] = -1;
            } else {
                next[last[color]] = i;
                prev[i] = last[color];
                last[color] = i;
            }
        }
        static int_t count[LARGE + 1];
        int_t curr = 0;
        for (int_t i = 1; i <= n; i++) {
            if (next[i] == 0) next[i] = n + 1;
            count[colors[i]] += 1;
            if (count[colors[i]] == 2) {
    #ifdef DEBUG
                cout << "color " << i << " = " << colors[i]
                     << " count = " << count[colors[i]] << endl;
    #endif
                curr += 1;
            }
            add(i, curr);
            add(i + 1, -curr);
        }
        int_t pos = 1;
        for (int_t i = 1; i <= m; i++) {
            auto& curr = opts[i];
            while (pos < curr.left) {
                int_t next0 = next[pos];
                if (next0 <= n) {
                    int_t next1 = next[next0];
                    add(next0, -1);
                    add(next1, 1);
                }
                pos++;
            }
            curr.result = query(curr.right);
        }
        std::sort(opts + 1, opts + 1 + m,
                  [](const Operation& a, const Operation& b) -> bool {
                      return a.id < b.id;
                  });
        for (int_t i = 1; i <= m; i++) {
            printf("%d\n", opts[i].result);
        }
        return 0;
    }