动态开点线段树

1. 105386J

抽象题面:带多重资源限制的最短路问题
在图上寻找最短路径,但每走一条边不仅看距离,还需要消耗特定种类的“资源”。且资源的使用有严格的顺序要求和单次容量上限。

核心思路:

  • 状态隔离:因为资源种类很多,如果混在一起处理会很慢。所以用线段树森林,为每种资源单独开一棵动态开点线段树,维护该区间内资源的最大可用容量。
  • 树上二分(加速转移):在跑 Dijkstra 寻路时,传统做法是挨个找下一个可用的资源,极其耗时。现在的做法是直接去对应资源的线段树上查,利用 findFirst(线段树上二分),以极快的速度(对数时间)跳过不合法的区间,精准定位到下一个容量达标的可用资源。
点击查看代码
struct Node {
    int v;
    int ticket;
    int curlen; // 当前用了多少
    bool operator<(const Node &t) const {
        if (ticket != t.ticket) return ticket > t.ticket;
        return curlen > t.curlen;
    }
};

struct node {
    int v;
    int l;
    int c;
};

const int N = 5e5 + 10;
vector<node> g[N];

template <class Info> struct DynamicSegmentTree {
    struct Node {
        int ls = 0; // 左孩子下标
        int rs = 0; // 右孩子下标
        Info info;
    };

    int n;
    vector<Node> nodes;

    DynamicSegmentTree(int n_) : n(n_) {
        nodes.push_back(Node{});
    }

    int newNode() {
        nodes.emplace_back();
        return (int)nodes.size() - 1;
    }

    // 上拉更新
    void pull(int p) {

        const Info &lhs = nodes[p].ls ? nodes[nodes[p].ls].info : Info();
        const Info &rhs = nodes[p].rs ? nodes[nodes[p].rs].info : Info();
        nodes[p].info = lhs + rhs;
    }

    // 如果是新的树开一个点
    // 如果是旧树不变
    int modify(int p, int l, int r, int x, const Info &v) {
        if (!p) p = newNode(); // 动态开点

        if (r - l == 1) {
            nodes[p].info = v;
            return p;
        }

        int m = l + (r - l) / 2;

        // 移除 push(p)

        if (x < m) {

            int new_ls = modify(nodes[p].ls, l, m, x, v);
            nodes[p].ls = new_ls;
        } else {
            int new_rs = modify(nodes[p].rs, m, r, x, v);
            nodes[p].rs = new_rs;
        }

        pull(p); // 更新当前节点信息
        return p;
    }

    // 外部接口:单点修改
    void modify(int &root, int x, const Info &v) {
        root = modify(root, 0, n, x, v);
    }

    // 区间查询
    Info rangeQuery(int p, int l, int r, int x, int y) {
        // 如果节点不存在,或者区间无交集,返回单位元
        if (!p || l >= y || r <= x) {
            return Info();
        }
        // 完全包含
        if (l >= x && r <= y) {
            return nodes[p].info;
        }

        int m = l + (r - l) / 2;

        return rangeQuery(nodes[p].ls, l, m, x, y) + rangeQuery(nodes[p].rs, m, r, x, y);
    }

    // 外部接口:区间查询
    Info rangeQuery(int root, int l, int r) {
        return rangeQuery(root, 0, n, l, r);
    }

    // 线段树二分:查找区间 [x, y) 内第一个满足 pred 的位置
    template <class F> int findFirst(int p, int l, int r, int x, int y, F &&pred) {
        if (!p || l >= y || r <= x) return -1;

        // 剪枝:如果当前区间的信息都不满足条件,直接返回
        if (l >= x && r <= y && !pred(nodes[p].info)) return -1;

        if (r - l == 1) return l;

        int m = l + (r - l) / 2;

        // 移除 push(p)

        int res = findFirst(nodes[p].ls, l, m, x, y, pred);
        if (res == -1) res = findFirst(nodes[p].rs, m, r, x, y, pred);
        return res;
    }

    template <class F> int findFirst(int root, int l, int r, F &&pred) {
        return findFirst(root, 0, n, l, r, pred);
    }
    // --- 你要求的 findLast ---
    template <class F> int findLast(int p, int l, int r, int x, int y, F &&pred) {
        if (!p || l >= y || r <= x) return -1;
        if (l >= x && r <= y && !pred(nodes[p].info)) return -1;
        if (r - l == 1) return l;

        int m = l + (r - l) / 2;

        // 优先找右边
        int res = findLast(nodes[p].rs, m, r, x, y, pred);
        if (res == -1) res = findLast(nodes[p].ls, l, m, x, y, pred);
        return res;
    }

    template <class F> int findLast(int root, int l, int r, F &&pred) {
        return findLast(root, 0, n, l, r, pred);
    }
};

struct Info {
    int mx = -1; // 默认为 0 或 -INF,视具体题目而定
};

// 重载 + 运算符用于合并
Info operator+(const Info &a, const Info &b) {
    Info res;
    res.mx = max(a.mx, b.mx);
    return res;
}

void solve() {

    int n, m, k;
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++)
        g[i].clear();
    vector<array<int, 2>> a(k + 1);
    for (int i = 1; i <= m; i++) {
        int u, v, c, l;
        cin >> u >> v >> c >> l;
        g[u].push_back({v, l, c});
        g[v].push_back({u, l, c});
    }
    DynamicSegmentTree<Info> seg(k + 1);
    vector<int> root(m + 1);
    for (int i = 1; i <= k; i++) {
        cin >> a[i][0] >> a[i][1];
        int id = a[i][0];
        // root[id]=seg.newTree();
        seg.modify(root[id], i, {a[i][1]});
    }

    auto dij = [&]() {
        vector<bool> vis(n + 1, 0);
        priority_queue<Node> q;
        q.push({1, 1, 0});
        while (q.size()) {
            auto [u, ticket, curlen] = q.top();
            q.pop();
            if (vis[u]) continue;
            vis[u] = 1;
            if (ticket > k) continue;
            for (auto [v, l, c] : g[u]) {
                // v是下一个点 l是长度  c是哪一个公司
                int nowG = a[ticket][0];
                int nowLimit = a[ticket][1];
                if (c == nowG && curlen + l <= nowLimit) {
                    // 走这个路
                    q.push({v, ticket, curlen + l});
                }
                auto cmp = [&](Info &info) { return info.mx >= l; };
                int next = seg.findFirst(root[c], ticket + 1, k + 1, cmp);
                if (next != -1) {
                    q.push({v, next, l});
                }
            }
        }
        for (int i = 1; i <= n; i++)
            if (vis[i])
                cout << 1;
            else
                cout << 0;
        cout << endl;
    };

    dij();
}

2. CF 915 E (Physical Education Lessons)

抽象题面:超大区间的状态覆盖与统计
在一个极大的范围(比如 10^9 长度)内,所有元素初始都是“状态 A”。我们需要频繁地把某一段区间全部覆盖为“状态 B”,或者恢复为“状态 A”,并随时查询当前“状态 A”的总数量。

核心思路:

  • 按需开点:因为范围太大,直接开满数组内存会爆,所以必须用动态开点,只有修改到的地方才分配内存。
  • 默认状态剪枝(防内存爆炸的核心):因为初始全是“状态 A”,如果遇到一个操作是“把某区间设为状态 A”,且发现线段树上这个区间的节点还没被创建出来,那就说明这地方本来就是默认的“状态 A”。此时直接 return 停止递归,绝对不要开新点,这样能省下大量无意义的内存消耗。
点击查看代码
struct Tag {
    int val = -1; // -1:无操作, 0:设为默认, 1:设为特殊

    // 是否有有效标记
    bool active() const {
        return val != -1;
    }

    // 标记合并:父节点的 Tag 覆盖/合并到子节点的 Tag
    void apply(const Tag &t) {
        if (t.active()) val = t.val;
    }

    // 【核心优化】判断是否为默认操作(即该操作不会改变默认状态的节点)
    // 用于剪枝:如果节点不存在,且操作是默认操作,就不需要新建节点
    bool is_default_modification() const {
        return val == 0;
    }
};

struct Info {
    int cnt = 0;

    // 将 Tag 应用到 Info 上(更新数值)
    void apply(const Tag &t, int l, int r) {
        if (t.active()) cnt = t.val * (r - l);
    }
};

Info operator+(const Info &a, const Info &b) {
    return {a.cnt + b.cnt};
}

template <class Info, class Tag> struct DynamicSegTree {
    struct Node {
        int ls = 0; // 左孩子下标
        int rs = 0; // 右孩子下标
        Info info;
        Tag tag;
    };

    int n;
    vector<Node> nodes;

    DynamicSegTree(int _n) : n(_n) {
        nodes.reserve(12000000); // 预分配防抖动
        nodes.emplace_back();    // 0: dummy node
    }

    int newNode() {
        nodes.emplace_back();
        return (int)nodes.size() - 1;
    }

    // --- 核心辅助函数 ---

    // 对节点 p 应用 Tag (更新 info 和 tag)
    void apply(int p, int l, int r, const Tag &v) {
        if (!p) return;
        nodes[p].info.apply(v, l, r);
        nodes[p].tag.apply(v);
    }

    // 下放标记 push
    // 注意:在动态开点中,push 意味着如果子节点不存在,必须创建出来接盘
    void push(int p, int l, int r) {
        if (!nodes[p].tag.active()) return;

        int mid = l + (r - l) / 2;
        Tag &tag = nodes[p].tag; // 取引用,后续清空

        // 处理左孩子
        if (!nodes[p].ls) {
            int id = newNode(); // 先获取id
            nodes[p].ls = id;   // 再赋值,防止 vector 扩容导致 nodes[p] 引用失效
        }
        apply(nodes[p].ls, l, mid, tag);

        // 处理右孩子
        if (!nodes[p].rs) {
            int id = newNode();
            nodes[p].rs = id;
        }
        apply(nodes[p].rs, mid, r, tag);

        // 清空当前节点标记
        tag = Tag();
    }

    // 上拉更新 pull
    void pull(int p) {
        nodes[p].info =
            (nodes[p].ls ? nodes[nodes[p].ls].info : Info()) + (nodes[p].rs ? nodes[nodes[p].rs].info : Info());
    }

    // --- 递归实现 (返回 int 下标) ---

    int rangeApply(int p, int l, int r, int x, int y, const Tag &v) {
        // 区间无交集
        if (l >= y || r <= x) return p;

        // 剪枝 2: 【关键优化】节点不存在,且当前操作是“无效操作”
        if (!p && v.is_default_modification()) return 0;

        // 如果节点不存在,必须新建(因为有操作要发生)
        if (!p) p = newNode();

        // 情况 1: 完全覆盖
        if (l >= x && r <= y) {
            apply(p, l, r, v);
            return p;
        }

        // 情况 2: 部分覆盖 -> 需要下放
        push(p, l, r);
        int mid = l + (r - l) / 2;

        // 递归左右,并更新左右孩子指针
        int new_ls = rangeApply(nodes[p].ls, l, mid, x, y, v);
        nodes[p].ls = new_ls;

        int new_rs = rangeApply(nodes[p].rs, mid, r, x, y, v);
        nodes[p].rs = new_rs;

        pull(p);
        return p;
    }

    Info rangeQuery(int p, int l, int r, int x, int y) {
        if (!p || l >= y || r <= x) return Info();
        if (l >= x && r <= y) return nodes[p].info;

        push(p, l, r);
        int mid = l + (r - l) / 2;

        return rangeQuery(nodes[p].ls, l, mid, x, y) + rangeQuery(nodes[p].rs, mid, r, x, y);
    }

    // Find First
    template <class F> int findFirst(int p, int l, int r, int x, int y, F &&pred) {
        if (!p || l >= y || r <= x) return -1;
        if (l >= x && r <= y && !pred(nodes[p].info)) return -1;
        if (r - l == 1) return l;

        push(p, l, r);
        int mid = l + (r - l) / 2;

        int res = findFirst(nodes[p].ls, l, mid, x, y, pred);
        if (res == -1) res = findFirst(nodes[p].rs, mid, r, x, y, pred);
        return res;
    }

    // Find Last
    template <class F> int findLast(int p, int l, int r, int x, int y, F &&pred) {
        if (!p || l >= y || r <= x) return -1;
        if (l >= x && r <= y && !pred(nodes[p].info)) return -1;
        if (r - l == 1) return l;

        push(p, l, r);
        int mid = l + (r - l) / 2;

        int res = findLast(nodes[p].rs, mid, r, x, y, pred);
        if (res == -1) res = findLast(nodes[p].ls, l, mid, x, y, pred);
        return res;
    }

    void rangeApply(int &root, int l, int r, const Tag &v) {
        root = rangeApply(root, 0, n, l, r, v);
    }

    // 2. 单点修改 (封装为区间修改)
    void modify(int &root, int p, const Tag &v) {
        root = rangeApply(root, 0, n, p, p + 1, v);
    }

    // 3. 区间查询
    Info rangeQuery(int root, int l, int r) {
        return rangeQuery(root, 0, n, l, r);
    }

    // 4. Find First
    template <class F> int findFirst(int root, int l, int r, F &&pred) {
        return findFirst(root, 0, n, l, r, pred);
    }

    // 5. Find Last
    template <class F> int findLast(int root, int l, int r, F &&pred) {
        return findLast(root, 0, n, l, r, pred);
    }
};

int main() {
    // 关闭流同步,加速 cin/cout,防止 I/O 耗时过长
    ios::sync_with_stdio(0);
    cin.tie(0);

    int n, q;
    // 读入总天数 n 和操作数 q
    if (!(cin >> n >> q)) return 0;

    // 创建线段树实例,管理范围 [0, n)
    DynamicSegTree<Info, Tag> seg(n + 1);
    int root = 0;

    while (q--) {
        int l, r, k;
        cin >> l >> r >> k;

        // 构造操作 Tag
        Tag t;
        t.val = (k == 1 ? 1 : 0); // k=1设为非工作日(1), k=2设为工作日(0)

        // 执行区间修改
        // 题目输入是闭区间 [l, r],线段树一般用左闭右开 [l, r+1)
        seg.rangeApply(root, l, r + 1, t);

        // 输出剩余的工作日天数
        // 总天数 n - 非工作日天数 (root节点的cnt)
        cout << n - seg.nodes[root].info.cnt << "\n";
    }

    return 0;

3. Mr. Liang's Sequence Problem II

抽象题面:大范围区间的数值加法与求和
维护一个极大的数组(值域很大),支持两种操作:把某一段区间内的所有数字统一加上一个值,以及查询某一段区间的数字总和。

核心思路:

  • 带长度的标记下放 (Push-down):这题虽然也是动态开点,但维护的是加法和求和。在下放“加法懒标记 (Lazy Tag)”时,新增的总和等于“加上的值 × 区间的长度”。所以在代码实现中,节点更新时必须显式地把区间长度传进去计算,不能像普通的单点覆盖那样直接赋值。
  • 防内存引用的“坑”:在动态建树向下递归开新点时,底层的 vector 数组可能会因为空间不够而自动扩容。为了防止扩容导致原来的指针/引用找错位置,必须老老实实地“先拿到新开点的编号,再把它赋值给父节点的左右儿子”,确保内存安全。
点击查看代码
using u64 = unsigned long long;
struct Tag {
    u64 add = 0;
    
    // 标记合并:数值叠加
    void apply(const Tag &t) {
        add += t.add;
    }
    
    // 判断标记是否为空(用于剪枝)
    bool empty() const {
        return add == 0;
    }
};

struct Info {
    u64 sum = 0;
    
    // 【关键修正】:必须传入区间长度 len
    void apply(const Tag &t, u64 len) {
        sum += t.add * len;
    }
};

// Info 合并
Info operator+(const Info &a, const Info &b) {
    return {a.sum + b.sum};
}

template<class Info, class Tag>
struct DynamicLazySegmentTree {
    struct Node {
        int ls = 0; 
        int rs = 0; 
        Info info;
        Tag tag;
    };

    u64 n; 
    std::vector<Node> nodes; 

    // 预估空间防止扩容慢
    DynamicLazySegmentTree(u64 n_) : n(n_) {
  
        nodes.push_back(Node{});
    }

    int newNode() {
        nodes.emplace_back();
        return (int)nodes.size() - 1;
    }

    void pull(int p) {
        // 必须用 nodes[...] 访问,防止引用失效
        const Info &lhs = nodes[p].ls ? nodes[nodes[p].ls].info : Info();
        const Info &rhs = nodes[p].rs ? nodes[nodes[p].rs].info : Info();
        nodes[p].info = lhs + rhs;
    }

    // 【关键修正】:apply 接收 l, r
    void apply(int p, u64 l, u64 r, const Tag &v) {
        nodes[p].info.apply(v, r - l); // 传入长度
        nodes[p].tag.apply(v);
    }

    // 【关键修正】:push 接收 l, r
    void push(int p, u64 l, u64 r) {
        // 剪枝:如果没有标记,坚决不开点!
        if (nodes[p].tag.empty()) return;

        if (!nodes[p].ls) nodes[p].ls = newNode(); 
        if (!nodes[p].rs) nodes[p].rs = newNode(); 
        
        u64 mid = l + (r - l) / 2;
        // 拷贝 tag 防止引用失效
        Tag t = nodes[p].tag;
        
        apply(nodes[p].ls, l, mid, t);
        apply(nodes[p].rs, mid, r, t);
        
        nodes[p].tag = Tag(); // 清空
    }

    // 区间修改
    int rangeApply(int p, u64 l, u64 r, u64 x, u64 y, const Tag &v) {
        if (l >= y || r <= x) return p;
        if (!p) p = newNode();

        if (l >= x && r <= y) {
            apply(p, l, r, v);
            return p;
        }
        
        u64 m = l + (r - l) / 2;
        push(p, l, r); 
        
        int new_ls = rangeApply(nodes[p].ls, l, m, x, y, v);
        nodes[p].ls = new_ls;
        
        int new_rs = rangeApply(nodes[p].rs, m, r, x, y, v);
        nodes[p].rs = new_rs;
        
        pull(p);
        return p;
    }
    
    // 区间查询
    Info rangeQuery(int p, u64 l, u64 r, u64 x, u64 y) {
        if (!p || l >= y || r <= x) return Info();
        if (l >= x && r <= y) return nodes[p].info;
        
        u64 m = l + (r - l) / 2;
        push(p, l, r);
        return rangeQuery(nodes[p].ls, l, m, x, y) + rangeQuery(nodes[p].rs, m, r, x, y);
    }

    // 外部接口
    void rangeApply(int &root, u64 l, u64 r, const Tag &v) {
        root = rangeApply(root, 0, n, l, r, v);
    }

    Info rangeQuery(int root, u64 l, u64 r) {
        return rangeQuery(root, 0, n, l, r);
    }
};



void  solve() {
    u64 n;
    int q;
     cin >> n >> q;

    // 实例化线段树
    DynamicLazySegmentTree<Info, Tag> seg(n + 1); // 这里的 n 只是范围上限
    int root = 0;

    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            u64 l, r, v;
            cin >> l >> r >> v;
            // 题目输入是闭区间 [l, r],代码习惯用左闭右开 [l, r+1)
            seg.rangeApply(root, l, r + 1, {v});
        } else {
            u64 l, r;
            cin >> l >> r;
            cout << seg.rangeQuery(root, l, r + 1).sum << "\n";
        }
    }


}

posted @ 2026-05-15 18:26  r_123  阅读(12)  评论(0)    收藏  举报