当前位置:首页 > 提高组 > (2018年NOIP提高组)洛谷P5021题:二分查找+动态规划解决赛道修建

(2018年NOIP提高组)洛谷P5021题:二分查找+动态规划解决赛道修建

4周前 (08-20)

(2018年NOIP提高组)洛谷P5021题:二分查找+动态规划解决赛道修建 图论算法 二分查找 动态规划 DFS 深度优先搜索 深搜 递归 树结构 NOIP 提高组 第1张

一、题目解读

洛谷P5021题(2018年NOIP提高组)要求在一棵带权中,查找满足路径权重大于等于指定阈值的最小阈值,同时保证符合条件的路径数量达到给定目标。问题本质是图论中的路径计数与二分查找的结合,需通过动态规划思想优化求解过程。

二、解题思路

采用二分查找阈值,将问题转化为“检查是否存在至少m条路径权重大于等于mid”。核心逻辑为:

1. 通过DFS遍历树,递归计算子树中以各节点为起点的最大路径权重;

2. 利用multiset存储未达标路径,通过二分匹配查找互补路径对,统计满足条件的路径数;

3. 动态调整阈值区间,直至找到符合条件的最小阈值。

三、解题步骤

1. 建与数据预处理:用vector存储树边信息,记录每条边的权重,初始化左右边界(路径权重范围);

2. 二分查找阈值:通过mid = (left + right) / 2迭代,调用DFS检查路径数量;

3. DFS递归计算:

○ 遍历当前节点的子树,递归获取子节点的最大路径权值(含边权);

○ 若子路径≥mid则计入cnt,否则存入multiset;

○ 通过multiset动态匹配路径对,更新cnt并维护最长单路径;

4. 阈值判定与收敛:若cnt≥m则左移区间(缩小阈值),反之右移,最终获取最小合法阈值。

四、代码与注释

#include <iostream>
#include <vector>
#include <algorithm>
#include<set>
using namespace std;

const int MAXN = 50010;
vector<pair<int, int>> tree[MAXN]; // 存储树边:节点-邻接点, 权重
int n, m, cnt; // n:节点数, m:目标路径数, cnt:当前合法路径计数

// 二分检查函数
int dfs(int u, int fa, int mid) { // u:当前节点, fa:父节点, mid:阈值
    multiset<int> s; // 存储未达标路径
    for (auto &edge : tree[u]) { // 遍历子树
        int v = edge.first, w = edge.second; // 邻接点v, 边权w
        if (v == fa) continue; // 跳过父节点
        int val = dfs(v, u, mid) + w; // 递归获取子树最大路径 + 边权
        if (val >= mid) cnt++; // 若≥阈值则计数
        else s.insert(val); // 否则存入multiset
    }
    
    int max_path = 0; // 记录当前节点最长单路径
    while (!s.empty()) { // 动态匹配路径对
        auto it = s.begin(); // 取最小未达标路径
        int x = *it;
        s.erase(it);
        auto match = s.lower_bound(mid - x); // 查找互补路径
        if (match!= s.end()) { // 若存在匹配
            cnt++; // 计数
            s.erase(match); // 删除配对路径
        } else { // 无匹配则更新最长单路径
            max_path = max(max_path, x);
        }
    }
    return max_path; // 返回当前节点最长单路径(用于后续递归)
}

int main() {
    cin >> n >> m; // 输入节点数n, 目标路径数m
    int left = 0, right = 0; // 初始化阈值区间
    for (int i = 1; i < n; i++) { // 建图
        int a, b, l;
        cin >> a >> b >> l; // 输入边信息
        tree[a].emplace_back(b, l); // 双向边
        tree[b].emplace_back(a, l);
        right += l; // 更新右边界(最大路径权)
    }
    
    int ans = 0; // 最终答案
    while (left <= right) { // 二分查找
        int mid = (left + right) / 2;
        cnt = 0; // 重置计数
        dfs(1, 0, mid); // 从根节点开始检查
        if (cnt >= m) { // 若路径数达标
            ans = mid; // 记录答案
            left = mid + 1; // 左移区间(缩小阈值)
        } else {
            right = mid - 1; // 右移区间(增大阈值)
        }
    }
    cout << ans << endl; // 输出结果
    return 0;
}

五、总结

该解法巧妙将路径阈值判定转化为二分查找,结合multiset动态匹配路径对,避免了暴力枚举的指数级复杂度。核心优化在于利用递归子树信息快速统计合法路径数,并通过区间收敛精准定位最小阈值。算法时间复杂度为O(nlogn),空间复杂度为O(n),适用于大规模图论场景。



原创内容 转载请注明出处

分享给朋友:

相关文章

CSP-J 2019纪念品题解(洛谷P5662):动态规划+完全背包问题的实战应用

CSP-J 2019纪念品题解(洛谷P5662):动态规划+完全背包问题的实战应用

一、题目解读2019年CSP-J的“纪念品”问题(对应洛谷P5662)要求玩家在T天内通过买卖纪念品最大化金币收益。每天可交易N种商品,需计算最优策略下的最终金币数。题目强调动态规划思维与资源分配优化...

手搓邻接矩阵类代码注释与实现指南:从零开始理解图论数据结构(适合小白)

一、简介和特点邻接矩阵是用于表示图(Graph)的一种数据结构,通常用二维数组存储节点之间的关系。本文的graph类通过C++实现了一个基础的邻接矩阵结构,特点如下:1. 动态创建:根据用户输入的节点...

LeetCode 120题三角形最小路径和最优解法:动态规划详解与代码实现

LeetCode 120题三角形最小路径和最优解法:动态规划详解与代码实现

一、题目解读LeetCode 120题“三角形最小路径和”要求给定一个由数字组成的三角形,从顶部开始向下移动,每次可向左或向右移动一格,计算从顶至底的最小路径和。三角形以二维向量形式给出,每层元素数量...

洛谷P4551题解题报告:图论与Trie树优化异或路径问题的实战解析

洛谷P4551题解题报告:图论与Trie树优化异或路径问题的实战解析

一、题目解读洛谷P4551题要求在一个无向图中,寻找任意两点路径权值异或后的最大值。题目输入为图的边信息(点数n和n-1条边),每条边包含起点、终点及权值。需输出所有路径中权值异或的最大值。问题核心在...

CSP-J 2019公交换乘题解析:基于队列优化的动态规划代码详解

CSP-J 2019公交换乘题解析:基于队列优化的动态规划代码详解

一、题目解读CSP-J 2019年的“公交换乘”题目(洛谷P5661)要求模拟地铁与公交交替出行的费用计算。题目核心在于地铁消费会产生优惠券,而公交可在45分钟内使用优惠券抵扣车费。需要处理n条出行记...

洛谷1111题解题全解析:基于Kruskal算法与并查集的最小生成树实现

洛谷1111题解题全解析:基于Kruskal算法与并查集的最小生成树实现

一、题目解读洛谷1111题是一道经典的图论问题,要求构建一个无向图的最小生成树,并输出其最大边权值。题目核心在于通过给定的边集合,找到连接所有节点的最小权值子集,同时保证无环。这通常涉及最小生成树算法...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。