算法笔试-图论

关于语言:一刷用 Golang,二刷用 Rust

题单来源于:分享丨【题单】图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)

题单

基础遍历 DFS

题目 来源 难度 分数
547. 省份数量 中等
1971. 寻找图中是否存在路径 简单
797. 所有可能的路径 中等

BFS 基础

拓扑排序

题目 来源 难度 分数
1557. 可以到达所有点的最少点数目 第 33 场双周赛 Q2 中等 1512
210. 课程表 II 中等

最短路

题目 来源 难度 分数
743. 网络延迟时间 中等
3112. 访问消失节点的最少时间 第 128 场双周赛 Q3 中等 1757
2642. 设计可以求最短路径的图类 第102 场周赛 Q4 困难 1811
3319. 第 K 大的完美二叉子树的大小 第 419 场周赛 Q2 中等 1603
1462. 课程表 IV 第 27 场双周赛 Q3 中等 1693
1334. 阈值距离内邻居最少的城市 第 173 场周赛 Q3 中等 1855
2976. 转换字符串的最小成本 I 第 377 场周赛 Q3 中等 1882

最小生成树

生成树(spanning tree):一个连通无向图的生成子图,同时要求是树。也即在图的边集中选择 \(n - 1\) 条,将所有顶点连通。

定义无向连通图的 最小生成树(Minimum Spanning Tree,MST)为边权和最小的生成树。

最小生成树算法,带输入与输出:

package main

import (
	"bufio"
	. "fmt"
	"os"
)

const (
	N   int = 510
	inf int = 2e9
)

var (
	in  *bufio.Reader
	out *bufio.Writer
)
var n, m int

func prim(dis [][]int, root int) (mstSum int) {
	// minD[i].d 表示 当前 MST 到点 i 的最小距离
	minD := make([]int, len(dis))
	for i := range minD {
		minD[i] = inf
	}
	minD[root] = 0
	// 初始所有的点都不在 MST 中
	inMST := make([]bool, len(dis))
	for i := 0; i < len(dis); i++ {
		v := -1
		for w, in := range inMST {
			if !in && (v < 0 || minD[w] < minD[v]) {
				v = w
			}
		}
		inMST[v] = true
		if i > 0 && minD[v] == inf {
			return inf
		}
		mstSum += minD[v]
		for w, d := range dis[v] {
			minD[w] = min(minD[w], d)
		}
	}
	return
}
func main() {
	in = bufio.NewReader(os.Stdin)
	out = bufio.NewWriter(os.Stdout)
	defer out.Flush()

	Fscan(in, &n, &m)

	g := make([][]int, n)
	for i := 0; i < n; i++ {
		g[i] = make([]int, n)
	}

	for i := 0; i < n; i++ {
		for j := 0; j < n; j++ {
			g[i][j] = inf
		}
	}

	var u, v, w int
	for i := 0; i < m; i++ {
		Fscan(in, &u, &v, &w)
		u, v = u-1, v-1
		g[u][v] = min(g[u][v], w)
		g[v][u] = min(g[v][u], w)
	}

	ans := prim(g, 0)
	if ans == inf {
		Fprintln(out, "impossible")
	} else {
		Fprintln(out, ans)
	}
}
//go:build ignore
// +build ignore

package main

import (
	"bufio"
	. "fmt"
	"os"

	"slices"
)

const inf int = 2e9

var (
	in   *bufio.Reader
	out  *bufio.Writer
	n, m int
)

func kruskal(n int, edges [][]int) int {
	slices.SortFunc(edges, func(a, b []int) int {
		return a[2] - b[2]
	})

	fa := make([]int, n)
	for i := range fa {
		fa[i] = i
	}

	var find func(int) int
	find = func(x int) int {
		if fa[x] != x {
			fa[x] = find(fa[x])
		}
		return fa[x]
	}

	sum, cnt := 0, 0
	for _, e := range edges {
		u, v, w := e[0], e[1], e[2]
		fu, fv := find(u), find(v)
		if fu != fv {
			fa[fv] = fu
			sum += w
			cnt++
		}
	}
	// 图不联通
	if cnt < n-1 {
		return inf
	}
	return sum
}

func main() {
	in = bufio.NewReader(os.Stdin)
	out = bufio.NewWriter(os.Stdout)
	defer out.Flush()

	Fscan(in, &n, &m)
	g := make([][]int, m)
	var u, v, w int
	for i := 0; i < m; i++ {
		Fscan(in, &u, &v, &w)
		u, v = u-1, v-1
		g[i] = []int{u, v, w}
	}

	ans := kruskal(n, g)
	if ans == inf {
		Fprintln(out, "impossible")
	} else {
		Fprintf(out, "%d\n", ans)
	}
}
题目 来源 难度 分数
1584. 连接所有点的最小费用 第 206 场周赛 Q3 中等 1858

连通分量

二分图

染色法判断是否是二分图:

func isBipartite(g [][]int) bool {
	colors := make([]int, len(g))
	var dfs func(int, int) bool
	dfs = func(u int, c int) bool {
		colors[u] = c
		for _, v := range g[u] {
			if colors[v] == c || colors[v] == 0 && !dfs(v, c ^ 1) {
				return false
			}
		}
		return true
	}
	for i, c := range colors {
		if c != 0 {
			continue
		}
		if !dfs(i, 2) {
			return false
		}
	}
	return true
}

匈牙利算法求最大匹配数:

func hungarian(g [][]int) (match []int, cnt int) {
	match = make([]int, n1 + n2 + 1)
	for i := range match {
		match[i] = -1
	}
	var used []bool
	var dfs func(int) bool
	dfs = func(u int) bool {
		for _, v := range g[u] {
			if !used[v] {
				used[v] = true
				if match[v] == -1 || dfs(match[v]) {
					match[v] = u
					return true
				}
			}
		}
		return false
	}
	for i := 1; i <= n1; i++ {
		used = make([]bool, n1 + n2 + 1)
        if dfs(i) {
			cnt++
		}
	}
	return
}

题目 来源 难度 分数
1093D. Beautiful Graph Educational Codeforces Round 56 (Rated for Div. 2) 1700

网络流

题解

547. 省份数量

func findCircleNum(isConnected [][]int) (ans int) {
    n := len(isConnected) 
    vis := make([]bool, n)
    var dfs func(int)
    dfs = func(i int) {
        if vis[i] {
            return
        }
        vis[i] = true
        for j, ok := range isConnected[i] {
            if ok == 1 {
                dfs(j)
            }
        }
    }
    for i := 0; i < n; i++ {
        if !vis[i] {
            dfs(i)
            ans++
        }
    }
    return
}

1971. 寻找图中是否存在路径

func validPath(n int, edges [][]int, source int, destination int) bool {
    g := make([][]int, n)
    for i := range g {
        g[i] = make([]int, 0)
    }
    for _, edge := range edges {
        u, v := edge[0], edge[1]
        g[u] = append(g[u], v)
        g[v] = append(g[v], u)
    }
    vis := make([]bool, n)
    var dfs func(int) bool
    dfs = func(u int) bool {
        if u == destination {
            return true
        }
        vis[u] = true
        for _, v := range g[u] {
            if !vis[v] && dfs(v) {
                return true
            }
        }
        return false
    }
    return dfs(source)
}

797. 所有可能的路径

func allPathsSourceTarget(graph [][]int) (ans [][]int) {
    path := []int{0}
    var dfs func(int)
    dfs = func(i int) {
        if i == len(graph) - 1 {
            ans = append(ans, append([]int{}, path...))
            return
        }
        for _, v := range graph[i] {
            path = append(path, v)
            dfs(v)
            path = path[:len(path) - 1]
        }
    }
    dfs(0)
    return
}

743. 网络延迟时间

仅此题目用朴素 dijkstra,堆优化 dijkstra,spfa 写,后续题目只选择最合适的。

func networkDelayTime(times [][]int, n int, k int) int {
    const inf = math.MaxInt / 2 // 防止加法溢出
    g := make([][]int, n + 1) // 邻接矩阵
    for i := range g {
        g[i] = make([]int, n + 1)
        for j := range g[i] {
            g[i][j] = inf
        }
    }

    for _, t := range times {
        u, v, w := t[0], t[1], t[2]
        g[u][v] = min(g[u][v], w)
    }

    dist := make([]int, n + 1)
    for i := range dist {
        dist[i] = inf
    }
    dist[0] = 0
    dist[k] = 0
    st := make([]int, n + 1)
    for i := 1; i < n; i++ {
        t := -1
        for j := 1; j <= n; j++ {
            if st[j] == 0 && (t == -1 || dist[t] > dist[j]) {
                t = j
            }
        }
        st[t] = 1
        for j := 1; j <= n; j++ {
            dist[j] = min(dist[j], dist[t] + g[t][j])
        }
    }

    ans := 0
    for _, v := range dist {
        ans = max(ans, v)
    }
    if ans == inf {
        return -1
    }
    return ans
}
const INF = 0x3f3f3f3f

type edge struct {
    to, w int
}

type hp []edge

func (h hp) Len() int              { return len(h) }
func (h hp) Less(i, j int) bool    { return h[i].w < h[j].w }
func (h hp) Swap(i, j int)         { h[i], h[j] = h[j], h[i] }
func (h *hp) Push(v interface{})   { *h = append(*h, v.(edge)) }
func (h *hp) Pop() (v interface{}) { a := *h; *h, v = a[:len(a)-1], a[len(a)-1]; return }

func networkDelayTime(times [][]int, n int, k int) int {

    g := make([][]edge, n+1)
    
    for i := range times {
        e := times[i]
        u, v, w := e[0], e[1], e[2]
        g[u] = append(g[u], edge{v, w})
    }
    
    dist := make([]int, n+1)
    for i := range dist {
        dist[i] = INF
    }
    dist[k] = 0
    
    h := hp{edge{k, 0}}
    for len(h) > 0 {
        cur := heap.Pop(&h).(edge)
        v, d := cur.to, cur.w
        if dist[v] < d {
            continue
        }
        for _, e := range g[v] {
            w := e.to
            if newDis := d + e.w; newDis < dist[w] {
                dist[w] = newDis
                heap.Push(&h, edge{w, newDis})
            }
        }
    }
    
    ans := 0
    for i := 1; i <= n; i++ {
        if dist[i] == INF {
            return -1
        }
        ans = max(ans, dist[i])
    }
    return ans
}
func networkDelayTime(times [][]int, n int, k int) int {
    const inf = math.MaxInt / 2
    g := make([][]edge, n + 1)
    for _, t := range times {
        u, v, w := t[0], t[1], t[2]
        g[u] = append(g[u], edge{v, w})
    }
    dist := make([]int, n + 1)
    for i := range dist {
        dist[i] = inf
    }
    dist[k] = 0

    pq := priorityqueue.NewWith(func (a, b interface{}) int {
        return a.(edge).w - b.(edge).w
    })
    pq.Enqueue(edge{k, 0})
    for pq.Size() > 0 {
        cur, _ := pq.Dequeue()
        v, d := cur.(edge).to, cur.(edge).w
        if dist[v] < d {
            continue
        }
        for _, e := range g[v] {
            w := e.to
            if newDis := d + e.w; newDis < dist[w] {
                dist[w] = newDis
                pq.Enqueue(edge{w, newDis})
            }
        }
    }

    ans := 0
    for i := 1; i <= n; i++ {
        if dist[i] == inf {
            return -1
        }
        ans = max(ans, dist[i])
    }
    return ans
}
type edge struct {
    to, w int
}
var (
    he, dist [110]int
    e, w, ne [6010]int
    st [100]bool
    idx = 0
)

const inf = math.MaxInt / 2

func add(a, b, c int) {
    w[idx] = c
    e[idx] = b
    ne[idx] = he[a]
    he[a] = idx
    idx++
}

func networkDelayTime(times [][]int, n int, k int) int {
    idx = 0
    for i := range dist {
        dist[i] = inf
    }
    for i := range he {
        he[i] = -1
    }
    for _, t := range times {
        u, v, w := t[0], t[1], t[2]
        add(u, v, w)
    }

    q := []int{}
    q = append(q, k)
    dist[k] = 0
    st[k] = true
    for len(q) > 0 {
        t := q[0]
        q = q[1:]
        st[t] = false
        for i := he[t]; i != -1; i = ne[i] {
            j := e[i]
            if dist[j] > dist[t] + w[i] {
                dist[j] = dist[t] + w[i]
                if !st[j] {
                    st[j] = true
                    q = append(q, j)
                }
            }
        }
    }

    ans := 0
    for i := 1; i <= n; i++ {
        if dist[i] == inf {
            return -1
        }
        ans = max(ans, dist[i])
    }
    return ans
}

3112. 访问消失节点的最少时间

func minimumTime(n int, edges [][]int, disappear []int) []int {
    g := make([][]edge, n)
    for _, e := range edges {
        u, v, w := e[0], e[1], e[2]
        g[u] = append(g[u], edge{v, w})
        g[v] = append(g[v], edge{u, w})
    }
    dist := make([]int, n)
    for i := range dist {
        dist[i] = -1
    }
    pq := priorityqueue.NewWith(func (a, b interface{}) int {
        return a.(edge).w - b.(edge).w
    })
    dist[0] = 0
    pq.Enqueue(edge{0, 0})
    for pq.Size() > 0 {
        cur, _ := pq.Dequeue()
        v, d := cur.(edge).to, cur.(edge).w
        if dist[v] < d {
            continue
        }
        for _, e := range g[v] {
            newDis := d + e.w
            if newDis < disappear[e.to] && (dist[e.to] == -1 || newDis < dist[e.to]) {
                dist[e.to] = newDis
                pq.Enqueue(edge{e.to, newDis})
            }
        }
    }
    return dist
}
type edge struct {
    to, w int
}

2642. 设计可以求最短路径的图类

type edge struct {
    v, w int
}

type Graph struct {
    n int
    g [][]edge
}


func Constructor(n int, edges [][]int) Graph {
    g := make([][]edge, n)
    for _, e := range edges {
        u, v, w := e[0], e[1], e[2]
        g[u] = append(g[u], edge{v, w})
    }
    return Graph{
        n: n,
        g: g,
    }
}


func (this *Graph) AddEdge(e []int)  {
    u, v, w := e[0], e[1], e[2]
    this.g[u] = append(this.g[u], edge{v, w})
}


func (this *Graph) ShortestPath(node1 int, node2 int) int {
    dist := make([]int, this.n)
    for i := range dist {
        dist[i] = -1
    }
    pq := priorityqueue.NewWith(func (a, b interface{}) int {
        return a.(edge).w - b.(edge).w
    })
    dist[node1] = 0
    pq.Enqueue(edge{node1, 0})
    for pq.Size() > 0 {
        cur, _ := pq.Dequeue()
        v, w := cur.(edge).v, cur.(edge).w
        if dist[v] < w {
            continue
        }
        for _, e := range this.g[v] {
            if newDis := w + e.w; (dist[e.v] == -1 || newDis < dist[e.v]) {
                dist[e.v] = newDis
                pq.Enqueue(edge{e.v, newDis})
            }
        }
    }
    return dist[node2]
}

3319. 第 K 大的完美二叉子树的大小

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func kthLargestPerfectSubtree(root *TreeNode, k int) int {
    var dfs func(*TreeNode) int
    ms := make([]int, 0)
    dfs = func(c *TreeNode) int {
        if c == nil {
            return 0
        }
        l, r := dfs(c.Left), dfs(c.Right)
        if l == -1 || r == -1 {
            return -1
        } else if l == r {
            ms = append(ms, l + r + 1)
            return l + r + 1
        } else {
            return -1
        }
    }
    dfs(root)
    slices.Sort(ms)
    if len(ms) < k {
        return -1
    }
    return ms[len(ms) - k]
}

1584. 连接所有点的最小费用

func minCostConnectPoints(points [][]int) int {
    const inf int = 2e9
    dis := func(a, b []int) int {
        return abs(a[0] - b[0]) + abs(a[1] - b[1])
    } 
    n := len(points)
    dist := make([][]int, len(points))
    for i := range dist {
        dist[i] = make([]int, n)
        for j := range dist[i] {
            dist[i][j] = dis(points[i], points[j])
        }
    }

    minD := make([]int, n)
    for i := range minD {
        minD[i] = inf
    }
    inMST := make([]bool, n)
    minD[0] = 0
    mstSum := 0
    for {
        v := -1
        for w, in := range inMST {
            if !in && (v < 0 || minD[w] < minD[v]) {
                v = w
            }
        }
        if v == -1 {
            break
        }
        inMST[v] = true
        mstSum += minD[v]
        for w, d := range dist[v] {
            minD[w] = min(minD[w], d)
        }
    }
    return mstSum
}

func abs(x int) int {
    if x < 0 {
        return -x
    }
    return x
}

1557. 可以到达所有点的最少点数目

func findSmallestSetOfVertices(n int, edges [][]int) (ans []int) {
    in := make([]bool, n)
	for _, e := range edges {
		in[e[1]] = true
	}
	for i, b := range in {
		if !b {
			ans = append(ans, i)
		}
	}
	return 
}

210. 课程表 II

func findOrder(numCourses int, prerequisites [][]int) (ans []int) {
    g := make([][]int, numCourses)
    for i := range g {
        g[i] = make([]int, 0)
    } 
    din := make([]int, numCourses)
    for _, p := range prerequisites {
        u, v := p[1], p[0]
        g[u] = append(g[u], v)
        din[v]++
    }
    q := []int{}
    for i := 0; i < numCourses; i++ {
        if din[i] == 0 {
            q = append(q, i)
        }
    }
    for len(q) > 0 {
        t := q[0]
        q = q[1:]
        ans = append(ans, t)
        for _, v := range g[t] {
            din[v]--
            if din[v] == 0 {
                q = append(q, v)
            }
        }
    }
    if len(ans) != numCourses {
        return []int{}
    }
    return 
}

1462. 课程表 IV

Floyd 求最短路

func checkIfPrerequisite(numCourses int, prerequisites [][]int, queries [][]int) []bool {
    g := make([][]bool, numCourses)
    for i := range g {
        g[i] = make([]bool, numCourses)
    } 

    for _, p := range prerequisites {
        u, v := p[0], p[1]
        g[u][v] = true
    }
    for k := 0; k < numCourses; k++ {
        for i := 0; i < numCourses; i++ {
            for j := 0; j < numCourses; j++ {
                if g[i][k] && g[k][j] {
                    g[i][j] = true
                }
            }
        }
    }
    ans := make([]bool, len(queries))
    for i, q := range queries {
        ans[i] = g[q[0]][q[1]]
    }
    return ans
}

1334. 阈值距离内邻居最少的城市

func findTheCity(n int, edges [][]int, distanceThreshold int) (ans int) {
    g := make([][]int, n)
    const inf int = 2e9
    for i := range g {
        g[i] = make([]int, n)
        for j := range g[i] {
            g[i][j] = inf
        }
    }
    for _, edge := range edges {
        u, v, w := edge[0], edge[1], edge[2]
        g[u][v] = w
        g[v][u] = w
    }

    for k := 0; k < n; k++ {
        for i := 0; i < n; i++ {
            for j := 0; j < n; j++ {
                g[i][j] = min(g[i][j], g[i][k] + g[k][j])
            }
        }
    }

    mn := n
    for i := 0; i < n; i++ {
        cnt := 0
        for j := 0; j < n; j++ {
            if i != j && g[i][j] <= distanceThreshold {
                cnt++
            }
        }
        if cnt <= mn {
            mn = cnt
            ans = i
        }
    }
    return
}

2976. 转换字符串的最小成本 I

func minimumCost(source string, target string, original []byte, changed []byte, cost []int) (ans int64) {
    g := make([][]int, 26)
    for i := range g {
        g[i] = make([]int, 26)
        for j := range g[i] {
            if i != j {
                g[i][j] = 1e13
            }
        }
    }
    for i := range original {
        u, v := original[i] - 'a', changed[i] - 'a'
        g[u][v] = min(g[u][v], cost[i])
    }
    for k := 0; k < 26; k++ {
        for i := 0; i < 26; i++ {
            for j := 0; j < 26; j++ {
                g[i][j] = min(g[i][j], g[i][k] + g[k][j])
            }
        }
    }

    for i := range source {
        u, v := source[i] - 'a', target[i] - 'a'
        ans += int64(g[u][v])
    }
    if ans >= 1e13 {
        return -1
    }
    return
}

1093D. Beautiful Graph

给出一个无向图,每个节点可以填 \(1,2,3\) 三个数中的一个。询问有多少种填数方案,使两个相邻节点之和为奇数。

如果图中有奇数环,一定无解。所以可以对图进行染色。记第 \(i\) 个联通分量的黑点数量为 \(b_i\),白点数量为 \(w_i\)。每一条连接的两个节点,一个是 \(2\),另一个是 \(1\)\(3\)。如果黑点全部填 \(2\),否则白点全部填 \(2\)。 - 若黑点填 \(2\),则剩下白点有 \(2^{w_i}\) 种填法。 - 若白点填 \(2\),则剩下的黑点有 \(2^{b_i}\) 种填法。

//go:build ignore
// +build ignore

package main

import (
	"bufio"
	. "fmt"
	"os"
)

var (
	in   *bufio.Reader
	out  *bufio.Writer
	n, m int
	g    [][]int
	pow2 [3e5 + 10]int64
)

const mod int64 = 998244353

func init() {
	const mx int = 3e5
	pow2 = [3e5 + 10]int64{1}
	for i := 1; i <= mx; i++ {
		pow2[i] = pow2[i-1] << 1 % mod
	}
}

func solve() {

	Fscan(in, &n, &m)
	g = make([][]int, n)
	var u, v int
	for ; m > 0; m-- {
		Fscan(in, &u, &v)
		u, v = u-1, v-1
		g[u] = append(g[u], v)
		g[v] = append(g[v], u)
	}

	cnt := [3]int{}
	colors := make([]int, n)
	var dfs func(int, int) bool
	dfs = func(u, c int) bool {
		colors[u] = c
		cnt[c]++
		for _, v := range g[u] {
			if colors[v] == c || colors[v] == 0 && !dfs(v, 3^c) {
				return false
			}
		}
		return true
	}
	ans := int64(1)
	for i, c := range colors {
		if c == 0 {
			cnt = [3]int{}
			if !dfs(i, 1) {
				Fprintln(out, 0)
				return
			}
			ans = ans * (pow2[cnt[1]] + pow2[cnt[2]]) % mod
		}
	}
	Fprintln(out, ans)
}

func main() {
	in = bufio.NewReader(os.Stdin)
	out = bufio.NewWriter(os.Stdout)
	defer out.Flush()

	var T int
	for Fscan(in, &T); T > 0; T-- {
		solve()
	}
}