时间:
分类: Algo

第一次遇到树上二分,记录一下。

说实话要是今年 CSP-S T1 出这个难度我可以在考场上上吊了。。。

这是赛后补的.jpg

看 dalao 们写的题解有点懵,花了亿点时间理解,所以这篇是 带注释ver.

题目

https://www.luogu.com.cn/problem/P11217

题意很好理解,但是要注意数据范围,由于所有数据不会有负数,所以就用 unsigned long long 好了。

思路

  • 20pts:模拟,只用线段树不用树上二分和模拟一样的(我就是只套了线段树.jpg)
  • 100pts:线段树 + 树上二分

代码

#include <iostream>
#include <vector>

#define ull unsigned long long // 不开 long long 会爆

class SegmentTree { // 线段树
private:
    struct Node {
        ull sum;
        ull lazy;
        Node *left, *right;
        Node() : sum(0), lazy(0), left(nullptr), right(nullptr) {};
    };
    
    std::vector<ull> init_vals;
    Node *root;
    ull n;
    
    Node *_build(ull begin, ull end) {
        Node *curr = new Node();
        if (begin == end) {
            curr->sum = init_vals.at(begin);
            return curr;
        }
        ull mid = begin + (end - begin) / 2;
        curr->left = _build(begin, mid);
        curr->right = _build(mid + 1, end);
        curr->sum = curr->left->sum + curr->right->sum;
        return curr;
    }
    
    void _push(Node *curr, ull begin, ull end, ull mid) { // 标准的 tag 下传
        if (begin == end) return;
        if (curr->lazy == 0) return;
        curr->left->lazy += curr->lazy;
        curr->right->lazy += curr->lazy;
        curr->left->sum += (mid - begin + 1) * curr->lazy;
        curr->right->sum += (end - mid) * curr->lazy;
        curr->lazy = 0;
    }

    ull _query_bs(Node *curr, ull begin, ull end, ull w, ull p) { // 树上二分,w 剩余生命值, p 要翻多少倍, sum * p 攻击力 
        ull result = 0;
        if (begin == end) {
            result += (curr->sum * p < w ? 1 : 0); // 攻击力小于 w 返回 1 否则 0, 一定是大于,按题目意思等于情况不计数
            return result;
        }
        ull mid = begin + (end - begin) / 2;
        _push(curr, begin, end, mid);
        if (curr->left->sum * p < w) { // 左节点总攻击力 (curr->left->sum * p) 小于剩余生命值 (w),一定是大于,按题目意思等于情况不计数
            result += mid - begin + 1; // 加上左节点覆盖的范围
            result += _query_bs(curr->right, mid + 1, end, (w - curr->left->sum * p), p); // 扫右节点, 剩余生命值减去左节点总攻击力
        } else { // 不然只扫左节点
            result += _query_bs(curr->left, begin, mid, w, p);
        }
        return result;
    }
    
    void _add(Node *curr, ull begin, ull end, ull l, ull r, ull d) { // 标准的区间加法
        if (begin > r || end < l) return;
        if (begin >= l && end <= r) {
            curr->lazy += d;
            curr->sum += (end - begin + 1) * d;
            return;
        }
        ull mid = begin + (end - begin) / 2;
        _push(curr, begin, end, mid);
        _add(curr->left, begin, mid, l, r, d);
        _add(curr->right, mid + 1, end, l, r, d);
        curr->sum = curr->left->sum + curr->right->sum;
    }
    
public:
    SegmentTree(std::vector<ull> _init_vals) {
        init_vals = _init_vals;
        n = _init_vals.size() - 1;
        root = _build(1, n);
    }

    ull query_bs(ull w, ull p) {
        return _query_bs(root, 1, n, w, p);
    }
    
    void add(ull l, ull r, ull d) {
        _add(root, 1, n, l, r, d);
    }

    ull total() { // 树的总攻击力
        return root->sum;
    }
};

ull sol(SegmentTree &seg, ull w, ull n) {
    ull result = 0;
    ull p = 1, tot = seg.total();
    while (w > tot) { // 一定是大于,按题目意思等于情况不计数
        w -= tot;
        p *= 2; // 翻倍计数
        tot *= 2; // 总攻击力翻倍
        result += n;
    }
    result += seg.query_bs(w, p); // 剩下的生命值再树上二分
    return result;
}

int main() {
    ull n, q, w;
    scanf("%llu%llu%llu", &n, &q, &w);
    
    std::vector<ull> a(n + 1);
    for (ull i = 1; i <= n; i++) scanf("%llu", &a[i]);
    
    SegmentTree seg(a);
    ull l, r, d;
    while (q--) {
        scanf("%llu%llu%llu", &l, &r, &d);
        seg.add(l, r, d);
        printf("%llu\n", sol(seg, w, n));
    }
    return 0;
}

标签: none


添加新评论

称呼