拼多多笔试真题-多多的告警网络(C++/Py/Java /Js/Go)

多多接金币

拼多多技术岗 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,则称它是可行方案。
    可行方案的费用是其中所有边的维护费用之和。
    请帮多多计算:
  1. 可行方案的最小费用;
  2. 费用最小的可行方案数量。

输入描述

第一行包含一个整数 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. 每条边有一个选择变量:1选择维护,0不选择维护。一个节点i如果连接它的边中,有奇数被选择,状态改变。偶数条边选择,状态不变。这个规律代表节点初始是 1,就必须被奇数条选择边影响;初始是 0,就必须被偶数条选择边影响

  2. 题目说明保证N节点 N边 相互连通,说明图由一个环 + 挂在环上的枝(可选), 其中枝的边选择一定是强制的,环中节点边存在选择自由

    复制代码
         1
        / \
       2---3
           |
           4
           |
           5

    例如示例1,4 5 肯定要想最后都为0,对应3-4边、4-5边是否选择会由 4 5节点初始状态决定。1 2 3 环中节点,都有两个边所以有自由选择

  3. 按照2的分析,首先利用拓扑排序剥离树枝节点,找出在环中的节点,并将环中节点按照相邻关系进行存储。

  4. 利用递归处理树枝节点边的选择方案,计算对应成本。同时更新与树枝相邻环节点的状态。

  5. 处理环中边的选择,这里枚举首条边的状态0 1即可,按照相邻边选择可以递推出所有边的选择关系,判断这种选择方案下环中节点是否能全为变为0?能变为0的情况下计算对应成本。

  6. 如果环中存在合法方案结果为 环处理成本 + 枝叶处理成本。

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])
		}
	}
}
相关推荐
无限码力3 天前
拼多多笔试真题-多多的GPU批处理调度(C++/Py/Java /Js/Go)
拼多多·pdd·拼多多笔试真题·拼多多机试·拼多多笔试·拼多多技术岗笔试真题
无限码力6 天前
拼多多笔试真题-多多的灰度发布(C++/Py/Java /Js/Go)
拼多多·拼多多机试·拼多多技术岗笔试·拼多多笔试·拼多多技术岗笔试真题
市象22 天前
新拼姆会是大号版“SHEIN”吗?
拼多多
无限码力25 天前
拼多多笔试真题【多多的特殊三元组】
拼多多·拼多多笔试真题·拼多多笔试题库·拼多多技术岗笔试·pdd笔试笔试真题
无限码力25 天前
拼多多笔试真题-多多的Boss挑战(C++/Py/Java /Js/Go)
拼多多·拼多多笔试真题·拼多多技术岗笔试题目·拼多多机试·pdd笔试真题
无限码力1 个月前
拼多多笔试真题-多多捕蝇(C++/Py/Java /Js/Go)
拼多多·拼多多笔试真题·拼多多技术岗笔试题目·拼多多机试·pdd笔试真题
无限码力1 个月前
拼多多笔试真题-多多的营救行动(C++/Py/Java /Js/Go)
拼多多·拼多多笔试真题·拼多多笔试题库·拼多多技术岗笔试
无限码力1 个月前
拼多多笔试真题-对角线遍历矩阵(C++/Py/Java /Js/Go)
矩阵·拼多多·拼多多笔试真题·拼多多技术岗笔试题目·拼多多机试
27669582921 年前
拼多多小程序 anti_content 分析
java·javascript·python·node·拼多多·anti-content·anti_content