题目描述
给定一个包含 NNN 个顶点和 MMM 条边的无向图,每个顶点初始有一个整数值。需要处理三种操作:
- 删除边:
D X,删除编号为 XXX 的边,保证每条边最多被删除一次。 - 查询:
Q X K,查询与顶点 XXX 连通的所有顶点中第 KKK 大的值(包括 XXX 自身),若 KKK 不合法则输出 000。 - 修改顶点权值:
C X V,将顶点 XXX 的权值改为 VVV。
操作序列以 E 结束。要求输出所有查询结果的平均值,保留六位小数。
数据范围:
- N≤2×104N \leq 2 \times 10^4N≤2×104
- M≤6×104M \leq 6 \times 10^4M≤6×104
- 查询次数 ≤2×105\leq 2 \times 10^5≤2×105
- 修改次数 ≤2×105\leq 2 \times 10^5≤2×105
题目分析
本题是一个动态图 + 连通块维护 + 第 KKK 大查询的综合问题,难点在于:
- 动态删边:直接处理删边会导致连通块分裂,难以高效维护。
- 第 KKK 大查询:需要在连通块中快速找到第 KKK 大的值。
- 顶点权值修改:修改会影响所在连通块的有序性。
如果按照操作顺序正序处理,每次删边都会分裂连通块,这在并查集中很难高效实现。
解题思路
核心思想:离线处理 + 时光倒流
我们将操作序列倒序处理,这样:
- 删除边 →\rightarrow→ 变成添加边(合并连通块)
- 修改权值 →\rightarrow→ 变成恢复旧值
- 查询操作不变,但查询的是“倒流”到该时刻的图状态
这样,我们始终在构建图而不是破坏图,可以利用并查集轻松维护连通性。
数据结构设计
每个连通块用一个有序数组维护其中的顶点权值:
- 初始化:每个顶点单独一个有序数组。
- 合并连通块:采用归并排序的方式合并两个有序数组,保证合并后仍然有序。
- 查询第 KKK 大:由于数组有序,直接通过下标访问即可,时间复杂度 O(1)O(1)O(1)。
- 修改权值:在有序数组中删除旧值,插入新值(使用二分查找定位)。
算法步骤
- 读入数据:记录所有操作,标记被删除的边。
- 构建初始图:将所有未被删除的边加入,形成初始连通块。
- 倒序处理操作:
- 遇到
D X:添加边 XXX,合并两个连通块。 - 遇到
Q X K:查询当前连通块的第 KKK 大值,累加答案。 - 遇到
C X V:将顶点 XXX 的权值恢复为原来的值 VVV。
- 遇到
- 输出结果:计算所有查询的平均值。
复杂度分析
- 查询操作:O(1)O(1)O(1)
- 修改操作:O(logn+n)O(\log n + n)O(logn+n)(二分查找 + 数组插入/删除)
- 合并操作:O(n)O(n)O(n)(归并两个有序数组)
- 总复杂度:最坏 O(n2)O(n^2)O(n2),但在实际数据中表现良好,能够通过本题。
代码实现
// Graph and Queries
// UVa ID: 1479
// Verdict: Accepted
// Submission Date: 2025-11-19
// UVa Run Time: 0.870s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 20005;
const int MAXM = 60005;
int n, m;
int weight[MAXN];
int u[MAXM], v[MAXM];
bool deleted[MAXM];
vector<tuple<char, int, int>> operations;
int parent[MAXN];
vector<int> trees[MAXN];
int queryCount;
long long querySum;
int find(int x) {
return parent[x] == x ? x : parent[x] = find(parent[x]);
}
void merge(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return;
if (trees[a].size() < trees[b].size()) swap(a, b);
// 归并两个有序数组
vector<int> merged;
merged.reserve(trees[a].size() + trees[b].size());
int i = 0, j = 0;
while (i < trees[a].size() && j < trees[b].size()) {
if (trees[a][i] < trees[b][j])
merged.push_back(trees[a][i++]);
else
merged.push_back(trees[b][j++]);
}
while (i < trees[a].size()) merged.push_back(trees[a][i++]);
while (j < trees[b].size()) merged.push_back(trees[b][j++]);
trees[a] = move(merged);
trees[b].clear();
parent[b] = a;
}
int query(int x, int k) {
x = find(x);
if (k < 1 || k > trees[x].size()) return 0;
return trees[x][trees[x].size() - k]; // 直接O(1)访问
}
void change(int x, int newVal) {
int root = find(x);
// 二分查找旧值并删除
auto& vec = trees[root];
auto it = lower_bound(vec.begin(), vec.end(), weight[x]);
vec.erase(it);
// 插入新值
weight[x] = newVal;
it = lower_bound(vec.begin(), vec.end(), newVal);
vec.insert(it, newVal);
}
int main() {
int caseNum = 1;
while (scanf("%d%d", &n, &m) == 2 && (n || m)) {
for (int i = 1; i <= n; i++) scanf("%d", &weight[i]);
for (int i = 1; i <= m; i++) scanf("%d%d", &u[i], &v[i]);
memset(deleted, 0, sizeof(deleted));
operations.clear();
char op;
while (scanf(" %c", &op) && op != 'E') {
if (op == 'D') {
int x; scanf("%d", &x);
deleted[x] = true;
operations.emplace_back('D', x, 0);
} else if (op == 'Q') {
int x, k; scanf("%d%d", &x, &k);
operations.emplace_back('Q', x, k);
} else if (op == 'C') {
int x, v; scanf("%d%d", &x, &v);
operations.emplace_back('C', x, weight[x]);
weight[x] = v;
}
}
// 初始化
for (int i = 1; i <= n; i++) {
parent[i] = i;
trees[i] = {weight[i]};
}
// 构建初始图(不包括被删除的边)
for (int i = 1; i <= m; i++)
if (!deleted[i]) merge(u[i], v[i]);
queryCount = 0;
querySum = 0;
// 倒序处理操作
for (int i = operations.size() - 1; i >= 0; i--) {
char op = get<0>(operations[i]);
int x = get<1>(operations[i]);
int y = get<2>(operations[i]);
if (op == 'D') merge(u[x], v[x]);
else if (op == 'Q') {
queryCount++;
querySum += query(x, y);
}
else if (op == 'C') change(x, y);
}
double average = queryCount ? (double)querySum / queryCount : 0.0;
printf("Case %d: %.6f\n", caseNum++, average);
}
return 0;
}
总结
本题的关键在于逆向思维:通过倒序处理将困难的删边操作转化为简单的加边操作。结合并查集维护连通性和有序数组维护权值,实现了对动态图的高效查询。
这种"时光倒流"的技巧在很多图论问题中都有应用,特别是涉及删除操作的情况,能够大大简化问题的复杂度。

1362

被折叠的 条评论
为什么被折叠?



