20250802-102508010-CP

正文

20250802

订正钉耙编程5

1. 1001 一个更无聊的游戏

题意

给定一棵 n n n 个节点的树,每个节点有一对参数 ( a i , b i ) . (a_i, b_i). (ai,bi). 在树上进行 q q q次游戏,每次给定一组 ( x , y ) , (x, y), (x,y), 允许你在节点 x x x放置一枚棋子在树上任意移动。游戏过程中,每当第一次经过某个节点时(包括初始时的节点 x x x),若 y ≥ a i , y \geq a_i, y≥ai, 则令 y ← y + b i ; y \leftarrow y + b_i; y←y+bi; 否则该轮游戏结束,得分为当前的 y y y值。求每次游戏的最大得分。

1 ≤ n , q ≤ 1 0 5 , ∑ n , ∑ q ≤ 5 × 1 0 5 ; 0 ≤ a i , b i , y ≤ 1 0 12 . 1 \leq n, q \leq 10 ^ 5, \sum n, \sum q \leq 5 \times 10 ^ 5; \space 0 \leq a_i, b_i, y \leq 10 ^ {12}. 1≤n,q≤105,∑n,∑q≤5×105; 0≤ai,bi,y≤1012.

做法

考虑暴力做法,每个关卡从起点开始遍历,把相邻的点加入一个堆,每次取 a i a_i ai 最小的点并扩展连通块。

考虑按照 a i a_i ai 从小到大依次加点,用 DSU 维护连通块以及其内可以抵达整个连通块的关卡(一定是在过程中逐渐减少的),加入一个点时再考虑会受到这个点限制的关卡,就是起点在与它相邻的联通块内的某些关卡,因为联通块内的点的所有 a j a_j aj 的限制都不及当前点,所以可以走遍连通块,吃遍联通块内所有 b j b_j bj,再加上它的初值与当前 a i a_i ai 比较。块内的 a j a_j aj 维护可以用堆或者平衡树,容易启发式合并,复杂度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)。

代码
cpp 复制代码
#include <bits/stdc++.h>
#define MP std::make_pair

using pii = std::pair<long long, int>;

void solve() {
    int n, Q;
    std::cin >> n >> Q;
    struct node {
        long long req, val;
        int i;
    };
    std::vector<node> a(n + 1);
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i].req;
        a[i].i = i;
    }
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i].val;
    }
    std::sort(a.begin() + 1, a.end(), [](const node x, const node y) {
        return x.req == y.req ? x.i < y.i : x.req < y.req;
    });
    std::vector<int> pos(n + 1);
    for (int i = 1; i <= n; i++) {
        pos[a[i].i] = i;
    }
    std::vector<std::vector<int> > g(n + 1);
    for (int i = 1; i < n; i++) {
        int u, v;
        std::cin >> u >> v;
        g[std::max(pos[u], pos[v])].push_back(std::min(pos[u], pos[v]));
    }
    std::vector<std::priority_queue<pii, std::vector<pii>, std::greater<pii> > > q(n + 1);
    std::vector<long long> ans(Q + 1, -1), init_val(Q + 1);
    for (int i = 1; i <= Q; i++) {
        long long x, y;
        std::cin >> x >> y;
        init_val[i] = y;
        if (y < a[pos[x]].req) {
            ans[i] = y;
        } else {
            q[pos[x]].push(MP(y, i));
        }
    }

    std::vector<long long> fa(n + 1), sum(n + 1);
    auto find = [&](auto self, int x) -> int {
        return fa[x] == x ? x : (fa[x] = self(self, fa[x]));
    };
    auto merge = [&](int x, int y) -> void {
        int rx = find(find, x), ry = find(find, y);
        if (rx == ry) {
            return;
        }
        fa[rx] = ry;
        sum[ry] += sum[rx];
        sum[rx] = 0;
        if (q[rx].size() > q[ry].size()) {
            std::swap(q[rx], q[ry]);
        }
        while (!q[rx].empty()) {
            q[ry].push(q[rx].top());
            q[rx].pop();
        }
    };
    for (int i = 1; i <= n; i++) {
        fa[i] = i;
        sum[i] = a[i].val;
    }
    for (int u = 1; u <= n; u++) {
        for (int v : g[u]) {
            int root = find(find, v);
            while (!q[root].empty()) {
                long long now = q[root].top().first + sum[root];
                if (now < a[u].req) {
                    ans[q[root].top().second] = now;
                    q[root].pop();
                } else {
                    break;
                }
            }
        }
        for (int v : g[u]) {
            merge(v, u);
        }
    }
    long long all_val = sum[find(find, 1)];
    for (int i = 1; i <= Q; i++) {
        if (ans[i] == -1) {
            std::cout << init_val[i] + all_val << '\n';
        } else {
            std::cout << ans[i] << '\n';
        }
    }
}

int main() {
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    std::cout.tie(0);
    int t;
    std::cin >> t;
    while (t--) {
        solve();
    }
}

20250803

2. 2126F - 1-1-1, Free Tree!

题意

给一棵树,每个点有颜色,每条边有权值,一棵树的总价值定义为每条边的价值和,每条边的价值为:如果这条边两端点颜色相同为 0 0 0,否则为这条边的权值。若干次操作:永久修改一个点的颜色,每次操作后输出这棵树的总价值。

做法

考虑暴力做法:维护 v a l ( u , c l r ) val(u,clr) val(u,clr) 表示从节点 u u u 出发的另一端颜色为 c l r clr clr 的边的权值和,由于要修改连接一个节点的所有边的另一端,所以容易被菊花图卡掉。以上做法的瓶颈在于一条边的权值在两个地方记录,维护需要考虑两端,并且单次操作要修改的内容过多,所以下面考虑优化这一点。

我们希望使得一条边的权值只在一个端点处被维护并且每次修改一个点时只需修改一个地方(因为总维护数为边数,而树上边数与点数几乎相等,所以希望一个点对应一条边),这样想到定根,每个点维护它的儿子边的信息,每次修改只需要往上修改父亲的信息即可。

代码

提交记录

相关推荐
:-)7 小时前
算法-归并排序
java·开发语言·数据结构·算法·排序算法
Jerry11 小时前
LeetCode 101. 对称二叉树
算法
可编程芯片开发12 小时前
基于MPPT最大功率跟踪的离网光伏发电系统Simulink建模与仿真
算法
AI科技星12 小时前
线性算子不是空间映射函数,是全域双螺旋场之间拉伸、旋转、耦合、坍缩的跨空间标准化变换载体《全域数学vs传统数学:人类文明进阶200讲》第80讲
线性代数·算法·矩阵·数据挖掘·回归·乖乖数学·全域数学
米罗篮12 小时前
矩阵快速幂 (Exponentiation By Squaring Applied To Matrices)
c++·线性代数·算法·矩阵
dream_home840713 小时前
图像算法模型NPU适配与算法服务实战指南
人工智能·python·算法·npu 图像服务
大鱼>13 小时前
多宠物家庭智能管理平台:云端架构与多设备协同实战
python·算法·iot·宠物
To_OC14 小时前
LC 22 括号生成:刷完这道题,我终于搞懂回溯剪枝了
javascript·算法·leetcode
To_OC14 小时前
LC 39 组合总和:回溯入门必刷题,我踩过的两个坑都在这了
javascript·算法·leetcode
数字杂技师14 小时前
每条推荐都很准,为什么我还是越刷越无聊?
算法