P11307 [COTS 2016] 建造费 Pristojba 分析
题目概述
这是一个 \(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;
}

浙公网安备 33010602011771号