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 的边的权值和,由于要修改连接一个节点的所有边的另一端,所以容易被菊花图卡掉。以上做法的瓶颈在于一条边的权值在两个地方记录,维护需要考虑两端,并且单次操作要修改的内容过多,所以下面考虑优化这一点。

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

代码

提交记录

相关推荐
2501_924878595 小时前
强光干扰下漏检率↓78%!陌讯动态决策算法在智慧交通违停检测的实战优化
大数据·深度学习·算法·目标检测·视觉检测
耳总是一颗苹果6 小时前
排序---插入排序
数据结构·算法·排序算法
YLCHUP6 小时前
【联通分量】题解:P13823 「Diligent-OI R2 C」所谓伊人_连通分量_最短路_01bfs_图论_C++算法竞赛
c语言·数据结构·c++·算法·图论·广度优先·图搜索算法
花火|7 小时前
算法训练营day62 图论⑪ Floyd 算法精讲、A star算法、最短路算法总结篇
算法·图论
GuGu20247 小时前
新手刷题对内存结构与形象理解的冲突困惑
算法
汤永红7 小时前
week4-[二维数组]平面上的点
c++·算法·平面·信睡奥赛
晴空闲雲7 小时前
数据结构与算法-字符串、数组和广义表(String Array List)
数据结构·算法
颜如玉9 小时前
位运算技巧总结
后端·算法·性能优化
冷月半明9 小时前
时间序列篇:Prophet负责优雅,LightGBM负责杀疯
python·算法
秋难降9 小时前
聊聊 “摸鱼式” 遍历 —— 受控遍历的小心机
数据结构·算法·程序员