#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// Use a large enough value for infinity.
// Sum of weights can be up to 2e5*1e9=2e14.
// Cycle weight can be positive or negative.
const long long INF = 4e18;
struct Edge {
int to;
int cost;
};
int N;
vector<long long> W;
vector<vector<Edge>> adj;
vector<long long> dp_down1; // max pathval(v, u) for v in subtree(u)
vector<long long> dp_down2; // max pathval(v, u) for v in subtree(u), v != u
long long max_cycle_weight;
// pathval(x, u) = S_W(x,u) - 2*S_C(x,u)
// Helper relation: for x in subtree of v, where v is child of u:
// pathval(x, u) = pathval(x, v) + W[u] - 2*cost(u,v)
void dfs(int u, int p) {
// Post-order traversal: first visit children
for (const auto& edge : adj[u]) {
int v = edge.to;
if (v == p) continue;
dfs(v, u);
}
// --- DP calculation for node u ---
long long max_child_term = -INF;
for (const auto& edge : adj[u]) {
int v = edge.to;
if (v == p) continue;
max_child_term = max(max_child_term, dp_down1[v] - 2LL * edge.cost);
}
if (max_child_term != -INF) {
dp_down2[u] = W[u] + max_child_term;
} else { // u is a leaf
dp_down2[u] = -INF;
}
dp_down1[u] = max(W[u], dp_down2[u]);
// --- Update global answer for paths with LCA = u ---
// Case 1: Endpoints in different child subtrees
vector<long long> child_branch_vals;
for (const auto& edge : adj[u]) {
int v = edge.to;
if (v == p) continue;
// value of max path from u down into v's branch
child_branch_vals.push_back(dp_down1[v] - 2LL * edge.cost + W[u]);
}
if (child_branch_vals.size() >= 2) {
sort(child_branch_vals.rbegin(), child_branch_vals.rend());
max_cycle_weight = max(max_cycle_weight, child_branch_vals[0] + child_branch_vals[1] - W[u]);
}
// Case 2: One endpoint is u, other is in a grandchild's subtree
vector<long long> grandchild_branch_vals;
for (const auto& edge : adj[u]) {
int v = edge.to;
if (v == p) continue;
if (dp_down2[v] != -INF) {
grandchild_branch_vals.push_back(dp_down2[v] - 2LL * edge.cost + W[u]);
}
}
if (!grandchild_branch_vals.empty()) {
sort(grandchild_branch_vals.rbegin(), grandchild_branch_vals.rend());
max_cycle_weight = max(max_cycle_weight, grandchild_branch_vals[0]);
}
}
void solve() {
cin >> N;
W.assign(N + 1, 0);
for (int i = 1; i <= N; ++i) {
cin >> W[i];
}
adj.assign(N + 1, vector<Edge>());
for (int i = 0; i < N - 1; ++i) {
int u, v, c;
cin >> u >> v >> c;
adj[u].push_back({v, c});
adj[v].push_back({u, c});
}
dp_down1.assign(N + 1, 0);
dp_down2.assign(N + 1, 0);
// Cycle weights can be negative, so initialize with a very small number.
max_cycle_weight = -INF;
// Start DFS from an arbitrary root, e.g., node 1.
// The logic is independent of the choice of root for the DFS traversal.
dfs(1, 0);
cout << max_cycle_weight << endl;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}