// プロジェクト選択問題(最小カット)。 // 総取り Σ(a_i + b_i) から「諦める分」を引く。諦める分の最小 = 最小カット。 // S→i に a_i (i を乙にすると a_i を失う) // i→T に b_i (i を甲にすると b_i を失う) // i↔j に c_j (別チームになると c_j を失う。両向き c_j) // 答え = Σ(a_i + b_i) - mincut function maxflow(n, edges, s, t) { // Dinic。edges は [from, to, cap] の配列(逆辺は自動で張る) const head = new Int32Array(n).fill(-1); const nxt = [], to = [], cap = []; const addEdge = (u, v, c, rc) => { to.push(v); cap.push(c); nxt.push(head[u]); head[u] = to.length - 1; to.push(u); cap.push(rc); nxt.push(head[v]); head[v] = to.length - 1; }; for (const [u, v, c, rc] of edges) addEdge(u, v, c, rc ?? 0); const level = new Int32Array(n); const iter = new Int32Array(n); const queue = new Int32Array(n); const bfs = () => { level.fill(-1); let qh = 0, qt = 0; level[s] = 0; queue[qt++] = s; while (qh < qt) { const v = queue[qh++]; for (let e = head[v]; e !== -1; e = nxt[e]) { if (cap[e] > 0 && level[to[e]] < 0) { level[to[e]] = level[v] + 1; queue[qt++] = to[e]; } } } return level[t] >= 0; }; const dfs = (v, f) => { if (v === t) return f; for (; iter[v] !== -1; iter[v] = nxt[iter[v]]) { const e = iter[v], u = to[e]; if (cap[e] > 0 && level[v] < level[u]) { const d = dfs(u, Math.min(f, cap[e])); if (d > 0) { cap[e] -= d; cap[e ^ 1] += d; return d; } } } return 0; }; let flow = 0; while (bfs()) { for (let i = 0; i < n; i++) iter[i] = head[i]; let f; while ((f = dfs(s, Infinity)) > 0) flow += f; } return flow; } function solveF(input) { const d = input.split(/\s+/).filter(s => s.length); let p = 0; const n = +d[p++], m = +d[p++]; const S = n, T = n + 1; const edges = []; let total = 0; for (let i = 0; i < n; i++) { const a = +d[p++], b = +d[p++]; total += a + b; edges.push([S, i, a]); edges.push([i, T, b]); } for (let j = 0; j < m; j++) { const u = +d[p++] - 1, v = +d[p++] - 1, c = +d[p++]; // 無向辺は「両方向に容量 c」で表す(逆辺も c) edges.push([u, v, c, c]); } return String(total - maxflow(n + 2, edges, S, T)); }; console.log(solveF(require("fs").readFileSync(0, "utf8")));