#include <bits/stdc++.h>
using namespace std;
// Dùng long long cho tổng trọng số MST để tránh tràn số
typedef long long ll;
const int INF = 1e9;
// Cấu trúc Disjoint Set Union (DSU) tiêu chuẩn
// Hỗ trợ nén đường đi (path compression) và gộp theo kích thước (union by size)
struct DSU {
vector<int> parent;
vector<int> sz; // Kích thước của thành phần liên thông
DSU(int n) {
parent.resize(n);
sz.resize(n);
reset(n);
}
void reset(int n) {
for(int i = 0; i < n; i++) {
parent[i] = i;
sz[i] = 1;
}
}
int find(int u) {
if (u == parent[u]) return u;
return parent[u] = find(parent[u]); // Path compression
}
// Trả về true nếu gộp thành công (tức là u và v chưa liên thông trước đó)
bool unite(int u, int v) {
u = find(u);
v = find(v);
if (u != v) {
if (sz[u] < sz[v]) swap(u, v);
parent[v] = u;
sz[u] += sz[v];
return true;
}
return false;
}
};
struct DynamicMST {
struct Edge {
int u, v, w;
int time_start, time_end; // Khoảng thời gian [L, R] mà cạnh này tồn tại/có trọng số w
// Sắp xếp cạnh theo trọng số tăng dần (để chạy Kruskal)
bool operator<(const Edge& other) const {
return w < other.w;
}
};
vector<Edge> all_edges; // Danh sách tất cả các phiên bản của cạnh theo thời gian
vector<array<int, 3>> init_edges; // Trạng thái ban đầu của các cạnh
vector<int> last_touched; // Thời điểm cuối cùng cạnh i được cập nhật
int num_nodes;
int current_query_time = 0;
// Hai DSU dùng cho 2 mục đích khác nhau trong thuật toán
DSU dsu_temp, dsu_contract;
vector<int> node_mapping; // Dùng để đánh số lại đỉnh sau khi co (contract)
vector<ll> results; // Lưu kết quả MST tại mỗi thời điểm
DynamicMST(vector<array<int, 3>> edges, int n)
: init_edges(edges), last_touched(edges.size()), num_nodes(n),
dsu_temp(n), dsu_contract(n), node_mapping(n) {
// Khởi tạo thời điểm last_touched của mọi cạnh là 0
}
// Hàm cập nhật trọng số cạnh thứ edge_idx thành weight_new
void update(int edge_idx, int weight_new) {
current_query_time++;
auto& [u, v, w_old] = init_edges[edge_idx];
// Tạo một phiên bản cạnh tồn tại từ lần sửa cuối đến hiện tại
all_edges.push_back({u, v, w_old, last_touched[edge_idx], current_query_time});
// Cập nhật thông tin mới
last_touched[edge_idx] = current_query_time;
w_old = weight_new; // Lưu trọng số mới vào mảng gốc để dùng cho lần update sau
}
// Hàm chính thực hiện thuật toán
vector<ll> run() {
int m = init_edges.size();
current_query_time++;
// Đẩy nốt các phiên bản cuối cùng của cạnh (tồn tại đến hết thời gian)
for(int i = 0; i < m; ++i) {
auto& [u, v, w] = init_edges[i];
all_edges.push_back({u, v, w, last_touched[i], current_query_time});
}
// Sắp xếp tất cả các cạnh theo trọng số ngay từ đầu
// Điều này giúp ta không phải sort lại trong đệ quy (tối ưu logN)
sort(all_edges.begin(), all_edges.end());
results.resize(current_query_time);
// Gọi đệ quy giải quyết đoạn thời gian [0, Q]
solve(0, current_query_time, all_edges, num_nodes, 0);
return results;
}
// Hàm đệ quy chia để trị: Xử lý đoạn thời gian [l, r]
// current_edges: Danh sách cạnh tiềm năng
// n: Số lượng đỉnh hiện tại (sau khi đã co bớt)
// current_mst_sum: Tổng trọng số các cạnh đã chắc chắn thuộc MST (bị co lại)
void solve(int l, int r, vector<Edge>& edges, int n, ll current_mst_sum) {
// 1. Lọc cạnh (Filter): Chỉ giữ lại các cạnh có tồn tại trong khoảng [l, r]
// Dùng stable_partition để đẩy các cạnh hợp lệ lên đầu mà KHÔNG làm mất thứ tự trọng số
auto it = stable_partition(edges.begin(), edges.end(), [&](const Edge& e) {
// Giữ lại nếu khoảng thời gian của cạnh giao với [l, r]
return !(e.time_end <= l || r <= e.time_start);
});
edges.erase(it, edges.end()); // Xóa các cạnh không liên quan
// Base case: Nếu l + 1 == r (đã đến 1 truy vấn cụ thể)
if (l + 1 == r) {
// Lúc này số lượng đỉnh n rất nhỏ, các cạnh đã lọc cũng rất ít
// Ta chạy Kruskal bình thường để tính nốt phần còn lại
dsu_temp.reset(n);
for (auto& e : edges) {
// Chỉ xét các cạnh bao trùm thời điểm này
if (e.time_start <= l && r <= e.time_end) {
if (dsu_temp.unite(e.u, e.v)) {
current_mst_sum += e.w;
}
}
}
results[l] = current_mst_sum;
return;
}
// --- BƯỚC 2: CONTRACTION (Co đỉnh) ---
// Mục tiêu: Tìm các cạnh TĨNH chắc chắn thuộc MST và co chúng lại.
dsu_temp.reset(n); // DSU dùng để check liên thông
dsu_contract.reset(n); // DSU dùng để lưu các đỉnh sẽ bị co
// 2a. Nối tất cả các cạnh ĐỘNG (Active edges - cạnh bị thay đổi trong [l, r])
// Ta coi như chúng có trọng số -vô cùng để ưu tiên nối trước.
for (auto& e : edges) {
if (l < e.time_start || e.time_end < r) { // Điều kiện cạnh động
dsu_temp.unite(e.u, e.v);
}
}
// 2b. Duyệt các cạnh TĨNH (Static edges - bao trùm cả [l, r])
// Vì edges đã sort theo w, nên ta đang xét từ nhẹ đến nặng.
for (auto& e : edges) {
if (e.time_start <= l && r <= e.time_end) { // Cạnh tĩnh
// Nếu cạnh tĩnh nối 2 thành phần mà ngay cả tất cả cạnh động cũng chưa nối được
// => Cạnh này BẮT BUỘC phải có trong MST.
if (dsu_temp.unite(e.u, e.v)) {
dsu_contract.unite(e.u, e.v); // Đánh dấu để lát nữa co đỉnh
current_mst_sum += e.w; // Cộng luôn vào kết quả
}
}
}
// --- BƯỚC 3: RELABELING (Đánh số lại đỉnh) ---
// Biến đồ thị to thành đồ thị nhỏ hơn dựa trên dsu_contract
int new_nodes_count = 0;
vector<int> component_id(n); // Mapping từ đỉnh cũ -> đỉnh mới
// Mỗi thành phần liên thông trong dsu_contract sẽ thành 1 đỉnh mới
for (int i = 0; i < n; ++i) {
if (dsu_contract.find(i) == i) {
component_id[i] = new_nodes_count++;
}
}
// Gán ID cho các đỉnh con
for (int i = 0; i < n; ++i) {
node_mapping[i] = component_id[dsu_contract.find(i)];
}
// --- BƯỚC 4: REDUCTION (Loại bỏ cạnh thừa) ---
// Mục tiêu: Loại bỏ các cạnh TĨNH tạo thành chu trình với các cạnh tĩnh tốt hơn.
// Cập nhật lại đỉnh u, v cho tất cả các cạnh theo ID mới
// Đồng thời check xem cạnh nào trở nên vô dụng (tự nối chính nó hoặc tạo chu trình)
dsu_temp.reset(new_nodes_count); // Reset DSU cho đồ thị mới (đã co)
// Ta cần duyệt lại edges để lọc.
// Logic: Các cạnh tĩnh đã co (ở bước 2) giờ thành khuyên (loop) u==v -> bỏ qua.
// Các cạnh tĩnh khác: Nếu tạo chu trình trong đồ thị mới -> bỏ qua (vì có cạnh khác tốt hơn thay thế rồi).
// Lưu ý: Code gốc thực hiện check reduction ngay trong lúc relabel
for (auto& e : edges) {
// Map u, v sang đỉnh mới
e.u = node_mapping[e.u];
e.v = node_mapping[e.v];
if (e.time_start <= l && r <= e.time_end) { // Với cạnh tĩnh
// Nếu 2 đầu mút trùng nhau (do đã bị co) -> Bỏ qua
// Nếu nối 2 đỉnh đã liên thông (tạo chu trình trong đồ thị mới) -> Bỏ qua
if (!dsu_temp.unite(e.u, e.v)) {
// Đánh dấu cạnh này là vô dụng bằng cách gán khoảng thời gian phi lý
// Nó sẽ bị xóa ở lệnh erase trong tầng đệ quy tiếp theo
e.time_start = INF;
e.time_end = -INF;
}
}
}
// --- BƯỚC 5: ĐỆ QUY ---
int mid = (l + r) / 2;
solve(l, mid, edges, new_nodes_count, current_mst_sum);
solve(mid, r, edges, new_nodes_count, current_mst_sum);
}
};
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n, m, q;
if (!(cin >> n >> m >> q)) return 0;
vector<array<int, 3>> edges(m);
for (auto& [u, v, w] : edges) {
cin >> u >> v >> w;
--u; --v; // Chuyển về 0-based index
}
DynamicMST mst_solver(edges, n);
for (int i = 0; i < q; ++i) {
int k, d;
cin >> k >> d;
--k; // 0-based index cho chỉ số cạnh
mst_solver.update(k, d);
}
auto answers = mst_solver.run();
// In kết quả từ 1 đến q (bỏ qua trạng thái 0 nếu đề bài không hỏi)
for (int i = 1; i <= q; ++i) {
cout << answers[i] << '\n';
}
return 0;
}