多多接金币
拼多多技术岗 7月19号笔试 第四题
题目内容
多多需要处理 TTT 套告警网络,每套网络由 NNN 个节点和 NNN 条无向边组成,没有自环和重边,并且任意两个节点之间都可以互相直达。
因此,这条网络中恰好存在一个简单环。
节点 iii 的初始告警状态为 bib_ibi:
- bi=0b_i = 0bi=0 表示告警已关闭;
- bi=1b_i = 1bi=1 表示告警正在响起。
第 iii 条边连接节点 ui,viu_i, v_iui,vi,维护这条边需要花费 cic_ici。维护一条边时,它两个端点的告警状态都会翻转:000 变为 111,111 变为 000。
一份维护方案是一个边的集合。集合中的每条边恰好维护一次,未被选中的边不进行维护。维护顺序不会影响最终状态,也不认为不同方案。
如果一份方案执行后所有节点的告警状态都变为 000,则称它是可行方案。
可行方案的费用是其中所有边的维护费用之和。
请帮多多计算:
- 可行方案的最小费用;
- 费用最小的可行方案数量。
输入描述
第一行包含一个整数 TTT,表示测试用例数量。
对于每个测试用例:
第一行包含一个整数 NNN,表示节点数量。
第二行包含 NNN 个整数 b1,b2,...,bNb_1,b_2,\dots,b_Nb1,b2,...,bN,表示各节点的初始告警状态。
接下来 NNN 行,第 iii 行包含三个整数 ui,vi,ciu_i, v_i, c_iui,vi,ci,表示第 iii 条边连接节点 ui,viu_i, v_iui,vi,维护费用为 cic_ici。
输出描述
对于每个测试用例输出一行。
如果不存在可行方案,输出 −1 0-1\ 0−1 0;
否则输出两个整数,依次表示最小费用和费用最小的可行方案数量。
补充说明:
- 1≤T≤31 \le T \le 31≤T≤3
- 3≤N≤2∗1053 \le N \le 2 * 10^53≤N≤2∗105
- bi∈{0,1}b_i \in \{0,1\}bi∈{0,1}
- 1≤ui,vi≤N, ui≠vi1 \le u_i,v_i \le N,\ u_i \ne v_i1≤ui,vi≤N, ui=vi
- 1≤ci≤1091 \le c_i \le 10^91≤ci≤109
输入的图联通,不含自环和重边。
样例1
输入
3
5
0 1 1 1 1
1 2 4
2 3 2
3 1 7
3 4 5
4 5 1
4
1 1 1 1
1 2 1
2 3 1
3 4 1
4 1 1
3
1 0 0
1 2 1
2 3 1
3 1 1
输出
3 1
2 2
-1 0
说明
第一个测试用例选择第 2、52、52、5 条边,费用为 333,另一份可行方案费用为 121212,因此答案为 3 13\ 13 1。
题解
思路
拓扑排序 + 逻辑分析
-
每条边有一个选择变量:1选择维护,0不选择维护。一个节点i如果连接它的边中,有奇数被选择,状态改变。偶数条边选择,状态不变。这个规律代表
节点初始是 1,就必须被奇数条选择边影响;初始是 0,就必须被偶数条选择边影响 -
题目说明保证
N节点 N边 相互连通,说明图由一个环 + 挂在环上的枝(可选), 其中枝的边选择一定是强制的,环中节点边存在选择自由1 / \ 2---3 | 4 | 5例如示例1,4 5 肯定要想最后都为0,对应3-4边、4-5边是否选择会由 4 5节点初始状态决定。1 2 3 环中节点,都有两个边所以有自由选择
-
按照2的分析,首先利用拓扑排序剥离树枝节点,找出在环中的节点,并将环中节点按照相邻关系进行存储。
-
利用递归处理树枝节点边的选择方案,计算对应成本。同时更新与树枝相邻环节点的状态。
-
处理环中边的选择,这里枚举首条边的状态
0 1即可,按照相邻边选择可以递推出所有边的选择关系,判断这种选择方案下环中节点是否能全为变为0?能变为0的情况下计算对应成本。 -
如果环中存在合法方案结果为 环处理成本 + 枝叶处理成本。
C++
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = 4e18;
struct Edge {
int to;
ll cost;
};
int N;
vector<vector<Edge>> g;
vector<int> b;
// 是否为环节点
vector<int> inCycle;
// 环节点剩余状态
vector<int> remainState;
// 树枝处理后的基础费用
ll baseCost = 0;
// DFS处理非环部分
// 返回:当前节点经过子树处理后,需要由父边解决的状态
int dfsTree(int u, int parent) {
int state = b[u];
for (auto &e : g[u]) {
int v = e.to;
// 父节点或者环节点跳过
if (v == parent || inCycle[v]) {
continue;
}
int childState = dfsTree(v, u);
// 需要选择该边
if (childState) {
baseCost += e.cost;
// 父节点状态需要反转
state ^= 1;
}
}
return state;
}
// 判断一种环边选择是否可行
// cycleNodes 按顺序存储环
// chooseFirst 表示第一条环边是否选择
vector<ll> solveCycle(vector<int>& cycleNodes) {
int m = cycleNodes.size();
vector<ll> edgeCost(m);
for (int i = 0; i < m; i++) {
int u = cycleNodes[i];
int v = cycleNodes[(i + 1) % m];
for (auto &e : g[u]) {
if (e.to == v) {
edgeCost[i]=e.cost;
break;
}
}
}
ll bestCost = INF;
int cnt = 0;
for (int first = 0; first <= 1; first++) {
vector<int> x(m);
x[0] = first;
bool ok = true;
for (int i = 1; i < m; i++) {
x[i] = x[i-1] ^ remainState[cycleNodes[i]];
}
// 检查最后一个节点
if ((x[m -1] ^ x[0]) != remainState[cycleNodes[0]]) {
ok = false;
}
if (!ok) {
continue;
}
ll cost = 0;
for (int i = 0; i < m; i++) {
if (x[i]) {
cost += edgeCost[i];
}
}
if (cost < bestCost) {
bestCost = cost;
cnt = 1;
} else if (cost == bestCost) {
cnt++;
}
}
if (bestCost == INF) {
return {-1, 0};
}
return {bestCost, cnt};
}
/*
恢复环的顺序
返回:
cycle[0]
cycle[1]
...
cycle[m-1]
其中相邻节点一定有环边
*/
vector<int> getCycleOrder() {
int start = -1;
for (int i = 1; i <= N; i++) {
if (inCycle[i]) {
start = i;
break;
}
}
vector<int> cycle;
int prev = -1;
int cur = start;
while (true) {
cycle.push_back(cur);
int nxt = -1;
for (auto &e : g[cur]) {
int v = e.to;
// 只能走环上节点
if (!inCycle[v]) {
continue;
}
// 不能走回
if (v == prev) {
continue;
}
nxt = v;
break;
}
prev = cur;
cur = nxt;
if (cur == start) {
break;
}
}
return cycle;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
cin >> N;
b.assign(N + 1,0);
for (int i = 1; i <= N; i++) {
cin >> b[i];
}
g.assign(N + 1,{});
// 每个节点的入度
vector<int> degree(N + 1);
for (int i = 0; i < N; i++) {
int u,v;
ll c;
cin >> u >> v >> c;
g[u].push_back({v, c});
g[v].push_back({u, c});
degree[u]++;
degree[v]++;
}
// 找出在环中的节点, 利用拓扑排序
inCycle.assign(N+1,1);
queue<int> q;
for (int i = 1; i <= N; i++) {
if (degree[i] == 1) {
q.push(i);
}
}
//不断找叶子节点
while (!q.empty()) {
int u = q.front();
q.pop();
inCycle[u] = 0;
for (auto &e : g[u]) {
int v = e.to;
if (inCycle[v]) {
degree[v]--;
if (degree[v] == 1) {
q.push(v);
}
}
}
}
baseCost = 0;
// 处理非环节点
remainState.assign(N+1,0);
vector<int> cycleNodes = getCycleOrder();
for (int u : cycleNodes) {
remainState[u] = dfsTree(u, -1);
}
// 处理环中节点
vector<ll> res = solveCycle(cycleNodes);
if (res[0] == -1) {
cout << "-1 0\n";
} else {
cout << baseCost + res[0] << " " << res[1] << endl;
}
}
return 0;
}
java
java
import java.io.*;
import java.util.*;
public class Main {
static final long INF = (long)4e18;
static class Edge {
int to;
long cost;
Edge(int to, long cost) {
this.to = to;
this.cost = cost;
}
}
static int N;
static ArrayList<Edge>[] g;
static int[] b;
// 是否为环节点
static int[] inCycle;
// 环节点剩余状态
static int[] remainState;
// 树枝处理后的基础费用
static long baseCost = 0;
// DFS处理非环部分
// 返回:当前节点经过子树处理后,需要由父边解决的状态
static int dfsTree(int u, int parent) {
int state = b[u];
for (Edge e : g[u]) {
int v = e.to;
// 父节点或者环节点跳过
if (v == parent || inCycle[v] == 1) {
continue;
}
int childState = dfsTree(v, u);
// 需要选择该边
if (childState == 1) {
baseCost += e.cost;
// 父节点状态需要反转
state ^= 1;
}
}
return state;
}
// 判断一种环边选择是否可行
// cycleNodes 按顺序存储环
static long[] solveCycle(ArrayList<Integer> cycleNodes) {
int m = cycleNodes.size();
long[] edgeCost = new long[m];
for (int i = 0; i < m; i++) {
int u = cycleNodes.get(i);
int v = cycleNodes.get((i + 1) % m);
for (Edge e : g[u]) {
if (e.to == v) {
edgeCost[i] = e.cost;
break;
}
}
}
long bestCost = INF;
int cnt = 0;
for (int first = 0; first <= 1; first++) {
int[] x = new int[m];
x[0] = first;
boolean ok = true;
for (int i = 1; i < m; i++) {
x[i] = x[i - 1] ^ remainState[cycleNodes.get(i)];
}
// 检查最后一个节点
if ((x[m - 1] ^ x[0]) != remainState[cycleNodes.get(0)]) {
ok = false;
}
if (!ok) {
continue;
}
long cost = 0;
for (int i = 0; i < m; i++) {
if (x[i] == 1) {
cost += edgeCost[i];
}
}
if (cost < bestCost) {
bestCost = cost;
cnt = 1;
} else if (cost == bestCost) {
cnt++;
}
}
if (bestCost == INF) {
return new long[]{-1, 0};
}
return new long[]{bestCost, cnt};
}
/*
恢复环的顺序
返回:
cycle[0]
cycle[1]
...
cycle[m-1]
其中相邻节点一定有环边
*/
static ArrayList<Integer> getCycleOrder() {
int start = -1;
for (int i = 1; i <= N; i++) {
if (inCycle[i] == 1) {
start = i;
break;
}
}
ArrayList<Integer> cycle = new ArrayList<>();
int prev = -1;
int cur = start;
while (true) {
cycle.add(cur);
int nxt = -1;
for (Edge e : g[cur]) {
int v = e.to;
// 只能走环上节点
if (inCycle[v] == 0) {
continue;
}
// 不能走回
if (v == prev) {
continue;
}
nxt = v;
break;
}
prev = cur;
cur = nxt;
if (cur == start) {
break;
}
}
return cycle;
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringBuilder sb = new StringBuilder();
int T = Integer.parseInt(br.readLine());
while (T-- > 0) {
N = Integer.parseInt(br.readLine());
b = new int[N + 1];
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 1; i <= N; i++) {
b[i] = Integer.parseInt(st.nextToken());
}
g = new ArrayList[N + 1];
for (int i = 1; i <= N; i++) {
g[i] = new ArrayList<>();
}
// 每个节点的入度
int[] degree = new int[N + 1];
for (int i = 0; i < N; i++) {
st = new StringTokenizer(br.readLine());
int u = Integer.parseInt(st.nextToken());
int v = Integer.parseInt(st.nextToken());
long c = Long.parseLong(st.nextToken());
g[u].add(new Edge(v, c));
g[v].add(new Edge(u, c));
degree[u]++;
degree[v]++;
}
// 找出在环中的节点, 利用拓扑排序
inCycle = new int[N + 1];
Arrays.fill(inCycle, 1);
Queue<Integer> q = new ArrayDeque<>();
for (int i = 1; i <= N; i++) {
if (degree[i] == 1) {
q.offer(i);
}
}
//不断找叶子节点
while (!q.isEmpty()) {
int u = q.poll();
inCycle[u] = 0;
for (Edge e : g[u]) {
int v = e.to;
if (inCycle[v] == 1) {
degree[v]--;
if (degree[v] == 1) {
q.offer(v);
}
}
}
}
baseCost = 0;
// 处理非环节点
remainState = new int[N + 1];
ArrayList<Integer> cycleNodes = getCycleOrder();
for (int u : cycleNodes) {
remainState[u] = dfsTree(u, -1);
}
// 处理环中节点
long[] res = solveCycle(cycleNodes);
if (res[0] == -1) {
sb.append("-1 0\n");
} else {
sb.append(baseCost + res[0])
.append(" ")
.append(res[1])
.append("\n");
}
}
System.out.print(sb);
}
}
python
python
import sys
from collections import deque
INF = 4e18
class Edge:
def __init__(self, to, cost):
self.to = to
self.cost = cost
N = 0
g = []
b = []
# 是否为环节点
inCycle = []
# 环节点剩余状态
remainState = []
# 树枝处理后的基础费用
baseCost = 0
# DFS处理非环部分
# 返回:当前节点经过子树处理后,需要由父边解决的状态
def dfsTree(u, parent):
global baseCost
state = b[u]
for e in g[u]:
v = e.to
# 父节点或者环节点跳过
if v == parent or inCycle[v]:
continue
childState = dfsTree(v, u)
# 需要选择该边
if childState:
baseCost += e.cost
# 父节点状态需要反转
state ^= 1
return state
# 判断一种环边选择是否可行
# cycleNodes 按顺序存储环
def solveCycle(cycleNodes):
m = len(cycleNodes)
edgeCost = [0] * m
for i in range(m):
u = cycleNodes[i]
v = cycleNodes[(i + 1) % m]
for e in g[u]:
if e.to == v:
edgeCost[i] = e.cost
break
bestCost = INF
cnt = 0
for first in range(2):
x = [0] * m
x[0] = first
ok = True
for i in range(1, m):
x[i] = x[i - 1] ^ remainState[cycleNodes[i]]
# 检查最后一个节点
if (x[m - 1] ^ x[0]) != remainState[cycleNodes[0]]:
ok = False
if not ok:
continue
cost = 0
for i in range(m):
if x[i]:
cost += edgeCost[i]
if cost < bestCost:
bestCost = cost
cnt = 1
elif cost == bestCost:
cnt += 1
if bestCost == INF:
return [-1, 0]
return [bestCost, cnt]
'''
恢复环的顺序
返回:
cycle[0]
cycle[1]
...
cycle[m-1]
其中相邻节点一定有环边
'''
def getCycleOrder():
start = -1
for i in range(1, N + 1):
if inCycle[i]:
start = i
break
cycle = []
prev = -1
cur = start
while True:
cycle.append(cur)
nxt = -1
for e in g[cur]:
v = e.to
# 只能走环上节点
if not inCycle[v]:
continue
# 不能走回
if v == prev:
continue
nxt = v
break
prev = cur
cur = nxt
if cur == start:
break
return cycle
def main():
global N, g, b, inCycle, remainState, baseCost
data = sys.stdin.buffer.read().split()
idx = 0
T = int(data[idx])
idx += 1
ans = []
while T:
T -= 1
N = int(data[idx])
idx += 1
b = [0] * (N + 1)
for i in range(1, N + 1):
b[i] = int(data[idx])
idx += 1
g = [[] for _ in range(N + 1)]
# 每个节点的入度
degree = [0] * (N + 1)
for _ in range(N):
u = int(data[idx])
v = int(data[idx + 1])
c = int(data[idx + 2])
idx += 3
g[u].append(Edge(v, c))
g[v].append(Edge(u, c))
degree[u] += 1
degree[v] += 1
# 找出在环中的节点, 利用拓扑排序
inCycle = [1] * (N + 1)
q = deque()
for i in range(1, N + 1):
if degree[i] == 1:
q.append(i)
#不断找叶子节点
while q:
u = q.popleft()
inCycle[u] = 0
for e in g[u]:
v = e.to
if inCycle[v]:
degree[v] -= 1
if degree[v] == 1:
q.append(v)
baseCost = 0
# 处理非环节点
remainState = [0] * (N + 1)
cycleNodes = getCycleOrder()
for u in cycleNodes:
remainState[u] = dfsTree(u, -1)
# 处理环中节点
res = solveCycle(cycleNodes)
if res[0] == -1:
ans.append("-1 0")
else:
ans.append(f"{baseCost + res[0]} {res[1]}")
print("\n".join(ans))
if __name__ == "__main__":
main()
javascript
js
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let lines = [];
rl.on("line", line => {
lines.push(line.trim());
});
rl.on("close", () => {
let idx = 0;
const T = Number(lines[idx++]);
let ans = [];
for (let t = 0; t < T; t++) {
const N = Number(lines[idx++]);
let b = [0];
let arr = lines[idx++].split(" ");
for (let i = 0; i < N; i++) {
b.push(Number(arr[i]));
}
let g = Array.from({ length: N + 1 }, () => []);
// 每个节点的入度
let degree = new Array(N + 1).fill(0);
class Edge {
constructor(to, cost) {
this.to = to;
this.cost = cost;
}
}
for (let i = 0; i < N; i++) {
let [u, v, c] = lines[idx++].split(" ").map(Number);
g[u].push(new Edge(v, c));
g[v].push(new Edge(u, c));
degree[u]++;
degree[v]++;
}
// 是否为环节点
let inCycle = new Array(N + 1).fill(1);
// 找出在环中的节点, 利用拓扑排序
let queue = [];
for (let i = 1; i <= N; i++) {
if (degree[i] === 1) {
queue.push(i);
}
}
let head = 0;
//不断找叶子节点
while (head < queue.length) {
let u = queue[head++];
inCycle[u] = 0;
for (let e of g[u]) {
let v = e.to;
if (inCycle[v]) {
degree[v]--;
if (degree[v] === 1) {
queue.push(v);
}
}
}
}
// 树枝处理后的基础费用
let baseCost = 0;
// 环节点剩余状态
let remainState = new Array(N + 1).fill(0);
// DFS处理非环部分
// 返回:当前节点经过子树处理后,需要由父边解决的状态
function dfsTree(u, parent) {
let state = b[u];
for (let e of g[u]) {
let v = e.to;
// 父节点或者环节点跳过
if (v === parent || inCycle[v]) {
continue;
}
let childState = dfsTree(v, u);
// 需要选择该边
if (childState) {
baseCost += e.cost;
// 父节点状态需要反转
state ^= 1;
}
}
return state;
}
/*
恢复环的顺序
返回:
cycle[0]
cycle[1]
...
cycle[m-1]
其中相邻节点一定有环边
*/
function getCycleOrder() {
let start = -1;
for (let i = 1; i <= N; i++) {
if (inCycle[i]) {
start = i;
break;
}
}
let cycle = [];
let prev = -1;
let cur = start;
while (true) {
cycle.push(cur);
let nxt = -1;
for (let e of g[cur]) {
let v = e.to;
// 只能走环上节点
if (!inCycle[v]) {
continue;
}
// 不能走回
if (v === prev) {
continue;
}
nxt = v;
break;
}
prev = cur;
cur = nxt;
if (cur === start) {
break;
}
}
return cycle;
}
// 判断一种环边选择是否可行
// cycleNodes 按顺序存储环
function solveCycle(cycleNodes) {
let m = cycleNodes.length;
let edgeCost = new Array(m).fill(0);
for (let i = 0; i < m; i++) {
let u = cycleNodes[i];
let v = cycleNodes[(i + 1) % m];
for (let e of g[u]) {
if (e.to === v) {
edgeCost[i] = e.cost;
break;
}
}
}
let bestCost = Number.MAX_SAFE_INTEGER;
let cnt = 0;
for (let first = 0; first <= 1; first++) {
let x = new Array(m).fill(0);
x[0] = first;
let ok = true;
for (let i = 1; i < m; i++) {
x[i] = x[i - 1] ^ remainState[cycleNodes[i]];
}
// 检查最后一个节点
if ((x[m - 1] ^ x[0]) !== remainState[cycleNodes[0]]) {
ok = false;
}
if (!ok) {
continue;
}
let cost = 0;
for (let i = 0; i < m; i++) {
if (x[i]) {
cost += edgeCost[i];
}
}
if (cost < bestCost) {
bestCost = cost;
cnt = 1;
} else if (cost === bestCost) {
cnt++;
}
}
if (bestCost === Number.MAX_SAFE_INTEGER) {
return [-1, 0];
}
return [bestCost, cnt];
}
// 处理非环节点
let cycleNodes = getCycleOrder();
for (let u of cycleNodes) {
remainState[u] = dfsTree(u, -1);
}
// 处理环中节点
let res = solveCycle(cycleNodes);
if (res[0] === -1) {
ans.push("-1 0");
} else {
ans.push(`${baseCost + res[0]} ${res[1]}`);
}
}
console.log(ans.join("\n"));
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
)
const INF int64 = 4e18
type Edge struct {
to int
cost int64
}
var (
N int
g [][]Edge
b []int
// 是否为环节点
inCycle []bool
// 环节点剩余状态
remainState []int
// 树枝处理后的基础费用
baseCost int64
)
// DFS处理非环部分
// 返回:当前节点经过子树处理后,需要由父边解决的状态
func dfsTree(u, parent int) int {
state := b[u]
for _, e := range g[u] {
v := e.to
// 父节点或者环节点跳过
if v == parent || inCycle[v] {
continue
}
childState := dfsTree(v, u)
// 需要选择该边
if childState == 1 {
baseCost += e.cost
// 父节点状态需要反转
state ^= 1
}
}
return state
}
// 判断一种环边选择是否可行
// cycleNodes 按顺序存储环
func solveCycle(cycleNodes []int) []int64 {
m := len(cycleNodes)
edgeCost := make([]int64, m)
for i := 0; i < m; i++ {
u := cycleNodes[i]
v := cycleNodes[(i+1)%m]
for _, e := range g[u] {
if e.to == v {
edgeCost[i] = e.cost
break
}
}
}
bestCost := INF
cnt := int64(0)
for first := 0; first <= 1; first++ {
x := make([]int, m)
x[0] = first
ok := true
for i := 1; i < m; i++ {
x[i] = x[i-1] ^ remainState[cycleNodes[i]]
}
// 检查最后一个节点
if (x[m-1]^x[0]) != remainState[cycleNodes[0]] {
ok = false
}
if !ok {
continue
}
var cost int64 = 0
for i := 0; i < m; i++ {
if x[i] == 1 {
cost += edgeCost[i]
}
}
if cost < bestCost {
bestCost = cost
cnt = 1
} else if cost == bestCost {
cnt++
}
}
if bestCost == INF {
return []int64{-1, 0}
}
return []int64{bestCost, cnt}
}
/*
恢复环的顺序
返回:
cycle[0]
cycle[1]
...
cycle[m-1]
其中相邻节点一定有环边
*/
func getCycleOrder() []int {
start := -1
for i := 1; i <= N; i++ {
if inCycle[i] {
start = i
break
}
}
cycle := []int{}
prev := -1
cur := start
for {
cycle = append(cycle, cur)
next := -1
for _, e := range g[cur] {
v := e.to
// 只能走环上节点
if !inCycle[v] {
continue
}
// 不能走回
if v == prev {
continue
}
next = v
break
}
prev = cur
cur = next
if cur == start {
break
}
}
return cycle
}
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var T int
fmt.Fscan(in, &T)
for ; T > 0; T-- {
fmt.Fscan(in, &N)
b = make([]int, N+1)
for i := 1; i <= N; i++ {
fmt.Fscan(in, &b[i])
}
g = make([][]Edge, N+1)
// 每个节点的入度
degree := make([]int, N+1)
for i := 0; i < N; i++ {
var u, v int
var c int64
fmt.Fscan(in, &u, &v, &c)
g[u] = append(g[u], Edge{v, c})
g[v] = append(g[v], Edge{u, c})
degree[u]++
degree[v]++
}
// 找出在环中的节点, 利用拓扑排序
inCycle = make([]bool, N+1)
for i := 1; i <= N; i++ {
inCycle[i] = true
}
queue := make([]int, 0)
for i := 1; i <= N; i++ {
if degree[i] == 1 {
queue = append(queue, i)
}
}
head := 0
//不断找叶子节点
for head < len(queue) {
u := queue[head]
head++
inCycle[u] = false
for _, e := range g[u] {
v := e.to
if inCycle[v] {
degree[v]--
if degree[v] == 1 {
queue = append(queue, v)
}
}
}
}
baseCost = 0
// 处理非环节点
remainState = make([]int, N+1)
cycleNodes := getCycleOrder()
for _, u := range cycleNodes {
remainState[u] = dfsTree(u, -1)
}
// 处理环中节点
res := solveCycle(cycleNodes)
if res[0] == -1 {
fmt.Fprintln(out, "-1 0")
} else {
fmt.Fprintln(out, baseCost+res[0], res[1])
}
}
}