P11307 [COTS 2016] 建造费 Pristojba 分析

news/2025/10/28 21:53:08/文章来源:https://www.cnblogs.com/high-sky/p/19172802

题目概述

这是一个 \(n\) 个点的无向图 \(G\),然后给你 \(m\) 次操作。

给你每个点的点权 \(p_i\),定义一条边 \((i,j)\) 的边权为 \(p_i+p_j\)

每个操作对应 \((x,l,r)\) 保证 \(x\notin [l,r]\)

然后对于所有的 \(y\in[l,r]\)\(x\)\(y\) 有一条连边。

求最小生成树的边权和。

分析

一道典型的菠萝算法。

我们先来讲一讲菠萝算法。

回顾

对于 \(Kruscal\) 算法和 \(Prim\) 算法,对于的是稀疏图的情况,而菠萝算法优势在于多边的情况(如完全图)。

而菠萝算法就是将两者结合起来。

\(Kruscal\) 算法可以看成一个城堡扩张,而 \(Prim\) 算法可以看成一个贪心去选最小的边去连接。

那么两者结合就得到了我们的菠萝算法。

我们还是使用 \(Kruscal\) 的算法区模拟每个城堡(以后就讲成连通块了)向外扩张。

怎么向外扩张呢?肯定是要找一个不在这个连通块内的连通块,于是我们利用 \(Prim\) 算法的贪心去找到最小的边更新,对于每一个连通块也是如此,这样我们的连通块个数减半,合并的次数为 \(\mathcal{O}(\log n)\) 次。

而我们合并时的做法是 \(\mathcal{O}(m\alpha(n)\log n)\) 的,而对于完全图可以达到 \(\mathcal{O}(n\alpha(n)\log n)\)

继续

怎么来呢?

这种多边问题不是考虑题目的特殊性质就是考虑菠萝算法。

没什么特殊性质啊(一般有特殊性质的可以直接用其他的算法),直接考虑菠萝算法。

注意到对于一条边 \((i,j)\) 它的边权为 \(p_i+p_j\)

也就是说,要找一条最小的边是容易的,直接找一个点权最小的即可。

我们现在要找边。

边分成两种情况:

  • 点到区间的边
  • 区间内的点到外面的点的边

考虑第一个情况,我们相当于需要枚举每个点 \(i\),所以说我们需要类似地去找最小的 \(p_x\),而线段树可以维护最小的值和连通块编号,但是有可能一样,所以我们再维护一个次小值以及它的连通块编号且这个编号不与最大值的那个编号一样,那么我们就愉快地解决这个问题了。

考虑第二个情况,我们依旧是枚举 \(i\) 然后用 \(i\) 这个点对区间里面的点进行更新,这个同样可以用线段树维护,于是这道题目就做完了。

代码

时间复杂度 \(\mathcal{O}(n\log^2 n)\),找 \(AI\) 写的,懒得打了,不过 AI 看了我的算法之后一遍过(doge)。

#include <bits/stdc++.h>
using namespace std;typedef long long ll;
const int INF = 1e9;
const int MAXN = 1e5 + 5;int n, m;
int p[MAXN];
vector<pair<int, int>> intervals[MAXN];// 并查集
struct DSU {vector<int> parent;DSU(int n) {parent.resize(n + 1);for (int i = 1; i <= n; i++) parent[i] = i;}int find(int x) {return parent[x] == x ? x : parent[x] = find(parent[x]);}bool unite(int x, int y) {x = find(x);y = find(y);if (x == y) return false;parent[x] = y;return true;}
};// 线段树节点:维护最小值和次小值(保证颜色不同)
struct Node {int min1, min2;    // 最小值和次小值int color1, color2; // 对应的连通块颜色Node() : min1(INF), min2(INF), color1(-1), color2(-1) {}Node(int val, int col) : min1(val), min2(INF), color1(col), color2(-1) {}// 合并两个节点Node operator+(const Node& other) const {vector<pair<int, int>> candidates;if (min1 < INF) candidates.push_back({min1, color1});if (min2 < INF) candidates.push_back({min2, color2});if (other.min1 < INF) candidates.push_back({other.min1, other.color1});if (other.min2 < INF) candidates.push_back({other.min2, other.color2});sort(candidates.begin(), candidates.end());Node res;if (candidates.empty()) return res;res.min1 = candidates[0].first;res.color1 = candidates[0].second;// 找第一个与color1不同的值作为次小值for (size_t i = 1; i < candidates.size(); i++) {if (candidates[i].second != res.color1) {res.min2 = candidates[i].first;res.color2 = candidates[i].second;return res;}}// 如果所有值颜色都相同res.min2 = INF;res.color2 = -1;return res;}
};// 第一棵线段树:用于区间查询(处理点->区间)
class SegmentTree1 {
private:vector<Node> tree;int n;void build(int idx, int l, int r, const vector<int>& colors) {if (l == r) {tree[idx] = Node(p[l], colors[l]);return;}int mid = (l + r) >> 1;build(idx << 1, l, mid, colors);build(idx << 1 | 1, mid + 1, r, colors);tree[idx] = tree[idx << 1] + tree[idx << 1 | 1];}Node query(int idx, int l, int r, int ql, int qr) {if (ql > r || qr < l) return Node();if (ql <= l && r <= qr) return tree[idx];int mid = (l + r) >> 1;if (qr <= mid) return query(idx << 1, l, mid, ql, qr);if (ql > mid) return query(idx << 1 | 1, mid + 1, r, ql, qr);return query(idx << 1, l, mid, ql, qr) + query(idx << 1 | 1, mid + 1, r, ql, qr);}public:SegmentTree1(int size, const vector<int>& colors) : n(size) {tree.resize(4 * n);build(1, 1, n, colors);}Node query(int l, int r) {if (l > r) return Node();return query(1, 1, n, l, r);}
};// 第二棵线段树:用于区间更新,单点查询(处理区间->点)
class SegmentTree2 {
private:vector<Node> tree, lazy;int n;void apply(int idx, const Node& val) {tree[idx] = tree[idx] + val;lazy[idx] = lazy[idx] + val;}void pushdown(int idx) {if (lazy[idx].min1 == INF) return;apply(idx << 1, lazy[idx]);apply(idx << 1 | 1, lazy[idx]);lazy[idx] = Node();}void update(int idx, int l, int r, int ul, int ur, const Node& val) {if (ul > r || ur < l) return;if (ul <= l && r <= ur) {apply(idx, val);return;}pushdown(idx);int mid = (l + r) >> 1;update(idx << 1, l, mid, ul, ur, val);update(idx << 1 | 1, mid + 1, r, ul, ur, val);tree[idx] = tree[idx << 1] + tree[idx << 1 | 1];}Node query(int idx, int l, int r, int pos) {if (l == r) return tree[idx];pushdown(idx);int mid = (l + r) >> 1;if (pos <= mid) return query(idx << 1, l, mid, pos);else return query(idx << 1 | 1, mid + 1, r, pos);}public:SegmentTree2(int size) : n(size) {tree.resize(4 * n);lazy.resize(4 * n);}void update(int l, int r, const Node& val) {if (l > r) return;update(1, 1, n, l, r, val);}Node query(int pos) {return query(1, 1, n, pos);}
};int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cin >> n >> m;for (int i = 1; i <= n; i++) {cin >> p[i];}for (int i = 0; i < m; i++) {int x, l, r;cin >> x >> l >> r;intervals[x].push_back({l, r});}DSU dsu(n);ll total_cost = 0;while (true) {// 检查是否所有点都已连通bool all_connected = true;int root = dsu.find(1);for (int i = 2; i <= n; i++) {if (dsu.find(i) != root) {all_connected = false;break;}}if (all_connected) break;// 获取当前连通块颜色vector<int> color(n + 1);for (int i = 1; i <= n; i++) {color[i] = dsu.find(i);}// 构建第一棵线段树(用于点->区间查询)SegmentTree1 seg1(n, color);// 初始化第二棵线段树(用于区间->点更新)SegmentTree2 seg2(n);// 存储每个连通块的最小出边vector<int> min_weight(n + 1, INF);vector<int> target_block(n + 1, -1);// 处理第一种情况:点->区间的边for (int i = 1; i <= n; i++) {int current_color = color[i];Node result;for (auto [l, r] : intervals[i]) {result = result + seg1.query(l, r);}// 使用结果更新最小边if (result.color1 != -1 && result.color1 != current_color) {int weight = result.min1 + p[i];if (weight < min_weight[current_color]) {min_weight[current_color] = weight;target_block[current_color] = result.color1;}}if (result.color2 != -1 && result.color2 != current_color) {int weight = result.min2 + p[i];if (weight < min_weight[current_color]) {min_weight[current_color] = weight;target_block[current_color] = result.color2;}}// 同时更新第二棵线段树:当前点可以连接到它的区间for (auto [l, r] : intervals[i]) {seg2.update(l, r, Node(p[i], current_color));}}// 处理第二种情况:区间->点的边for (int i = 1; i <= n; i++) {int current_color = color[i];Node result = seg2.query(i);// 计算完整边权if (result.min1 < INF) {int weight1 = result.min1 + p[i];if (result.color1 != -1 && result.color1 != current_color) {if (weight1 < min_weight[current_color]) {min_weight[current_color] = weight1;target_block[current_color] = result.color1;}}}if (result.min2 < INF) {int weight2 = result.min2 + p[i];if (result.color2 != -1 && result.color2 != current_color) {if (weight2 < min_weight[current_color]) {min_weight[current_color] = weight2;target_block[current_color] = result.color2;}}}}// 合并连通块vector<tuple<int, int, int>> edges;for (int i = 1; i <= n; i++) {if (dsu.find(i) == i && target_block[i] != -1 && min_weight[i] < INF) {edges.emplace_back(min_weight[i], i, target_block[i]);}}// 按边权排序并合并sort(edges.begin(), edges.end());bool merged = false;for (auto [w, u, v] : edges) {if (dsu.unite(u, v)) {total_cost += w;merged = true;}}// 如果没有合并任何边,说明图不连通但找不到更多边(根据题目保证不会发生)if (!merged) {//无用的废话cerr << "Warning: No edges merged in this round" << endl;break;}}cout << total_cost << endl;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/949164.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序员如何打破职业瓶颈?先搬开这3块绊脚石。

程序员的职业路不算长,可不少人早早就遇到了 “天花板”:想提升没方向,想突破没抓手,这就是让人头疼的 “瓶颈期”。是什么让我们陷入这种困境?又该怎么走出来?咱们一点点说清楚。 摆烂心态 在程序员不算长的职业…

文件清理,推荐几款常用软件

文件清理,推荐几款常用软件给大家汇总如下表格,根据自己的情况自行选择: 您的资助是我最大的动力!金额随意,欢迎来赏!付款后有任何问题请给我留言。 如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的…

AI时代的设计师:从工具到“超人”的进化之路

最近刷到几则关于AI和设计领域的新闻,感觉挺有意思的。站酷发布了AI设计Agent,美图CEO吴欣鸿在演讲中提到AI正让设计师变成“超人”,而另一边,Meta的裁员风波又让人对AI行业的就业前景捏把汗。先说说AI工具如何改变…

MyBatis 动态 SQL 实现原理 - Higurashi

总结自(部分调整):MyBatis 3 源码深度解析SqlSource 与 BoundSql 详解 在介绍原理之前,我们首先需要了解 MyBatis 中和 SQL 语句有关的两个组件,即 SqlSource 和 BoundSql。SqlSource:代表 Java 注解或者 XML 文…

bililun

实验任务1 源代码 T.cpp#include "T.h" #include <iostream> #include <string>// 类T实现// static成员数据类外初始化 const std::string T::doc{"a simple class sample"}; const…

《程序员修炼之道:从小工到专家》观后感第二篇

《代码之美——DRY原则与简洁代码的实效哲学》 核心观点:“不要重复自己(DRY)”是编码的黄金法则,冗余代码会直接推高维护成本与bug发生率,而简洁、自解释的代码是系统可维护性的核心保障。 案例分析:某社交平台…

【学习笔记】数据结构全家桶

Data Structure前言 2025.10.28: 一时兴起建了这个,原因是刚刚学了KTT qwq 我如果塞完这个东西应该都大学几年级了吧 没事慢慢弄 正文 线段树 KTT点我展开看代码 #include<bits/stdc++.h> #define rep(i, l, r…

社区

社区报告名称:AR(Active Area Reverse)fin cut工艺参数与表征指标数据分析报告 版本:V1.0 日期:2025-09-05 编制人:李晓睿 审核人:[姓名/部门] 1. 数据收集报告 1.1 数据来源来源:AR(AA Reverse)fin cut工艺…

「Gym 102759I」Query On A Tree 17

题目大意 给定一颗 \(N\) 个节点以 \(1\) 为根的有根树,每次给以 \(u\) 为根的子树每点加 \(1\) 的值或给路径 \(u - v\) 上每点加 \(1\) 的值,每次修改后查询一个点 \(u\) 使得 \(\sum_{v = 1}^N dis(u, v)\) 最小。…

Mybatis使用简述

什么是 MyBatis? MyBatis 是一款优秀的 Java 持久层框架,它通过 XML 或注解的方式将 Java 对象与数据库中的记录进行映射。与传统的 JDBC 相比,MyBatis 极大地简化了数据库操作代码,让开发者能够更专注于业务逻辑而…

重组蛋白表达服务:CHO HEK293细胞系选择与表达优化方案

重组蛋白表达服务:CHO/HEK293细胞系选择与表达优化方案在生命科学研究领域,重组蛋白作为重要的科研试剂,其质量直接影响实验结果的可靠性。选择合适的表达系统并进行表达优化,是获得高质量重组蛋白的关键环节。本文…

C++里的代码命名规范

以下是 C++ 中最主流的几种命名规范: 1. 蛇形命名法 这是 C++ 标准库和许多 C++ 社区(如 Boost)最推荐的风格。小写蛇形命名法:所有字母小写,单词之间用下划线 _ 连接。适用于:变量、函数、命名空间、文件。 示例…

最小二乘问题详解6:梯度下降法

介绍了使用梯度下降法求解非线性最小二乘问题的原理与实现,通过C++和Eigen库对指数模型进行参数拟合,展示了算法流程、雅可比矩阵计算及收敛行为,并对比了其在机器学习与计算机视觉中的应用差异。1. 引言 在之前的两…

JavaWeb01

1.JavaWeb介绍 什么是JavaWeb?Web:全球广域网,也称万维网(www),能够通过浏览器访问的网站 JavaWeb:使用Java技术来解决相关web互联网领域的技术栈网页:展现数据 数据库:存储和管理数据 JavaWeb程序:逻辑处理数…

现代C++编程初体验

##实验任务1 ##代码#pragma once#include <string>// 类T: 声明 class T { // 对象属性、方法 public:T(int x = 0, int y = 0); // 普通构造函数T(const T &t); // 复制构造函数T(T &&t); …

Delphi 利用接口实现frame窗体间的通讯(互动)

需求说明: 程序设计:效果演示:设计思路: FrmCK 只负责发布事件,不关心谁在监听. FrmGrid 只负责响应事件,不关心事件来源. 创建过程: 一.创建接口单元FrmInterface. 全部代码如下:unit FrmInterface;interfaceusessy…

Python冒泡排序:简单易懂的算法实现

在编程的世界里,排序算法是数据处理的基础之一。冒泡排序(Bubble Sort)是一种简单且直观的排序算法,虽然它的效率不是最高的,但它非常适合初学者学习排序算法的基本概念。今天,我们就来详细探讨如何在Python中实…

SAM+ARM

一、首先是图像caption的生成。 输入的图像,被输入进BLIP的图像编码器得到图像嵌入,图像嵌入再经过(BLIP Image-grounded Text Decoder) 得到图像caption。ti表示caption的第i个单词,总共有L个单词。 但是,capti…

《代码大全2》观后感(二):需求分析——代码质量的“源头防线”

《代码大全2》观后感(二):需求分析——代码质量的“源头防线” “为什么明明按需求写的代码,最后还是要推翻重写?”这是我过去常有的困惑,直到读了《代码大全2》中“需求分析”的章节,才找到答案:很多时候,我…