首页 > 试题广场 >

在树上游玩

[编程题]在树上游玩
  • 热度指数:800 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 1024M,其他语言2048M
  • 算法知识视频讲解
\hspace{15pt}对于给定的由 n 个节点组成的无根树,每一条边都可以被染上颜色,初始时,全部边均为白色。
\hspace{15pt}现在,选中树上 k 个不同的点,并将它们标记,随后,定义,如果一条树边 (u, v) 满足节点 uv 同时被标记,那么这条树边自动被染为红色,不需要花费任何代价。

\hspace{15pt}现在,你可以额外选择一些树边,将它们染成红色,每染一条边需要花费 1 点代价。
\hspace{15pt}请你计算最小的染色代价,使得任意一个被标记的点都可以通过被染成红色的边到达至少一个未被标记的点。并输出不同的染色方案数量。

输入描述:
\hspace{15pt}第一行输入两个整数 n,k\left(2\leqq n\leqq 10^5;\ 1\leqq k\lt n\right) 代表树的节点数、被标记的点数。
\hspace{15pt}第二行输入 k 个不同的整数 a_1,a_2,\dots,a_k\left(1\leqq a_i\leqq n\right) 代表被标记的点的编号。
\hspace{15pt}此后 n-1 行,第 i 行输入两个整数 u_i,v_i\left(1\leqq u_i,v_i\leqq n,u_i\neq v_i\right) 代表第 i 条树边连接 u_iv_i


输出描述:
\hspace{15pt}在一行上输出两个整数,代表最小的染色代价、满足条件的染色方案数量。由于染色方案数量可能很大,请输出对 10^9+7 取模后的结果。
示例1

输入

11 6
8 2 4 7 9 6
8 10
8 9
1 8
1 11
1 3
11 2
5 6
2 5
4 2
7 6

输出

3 4

说明

\hspace{15pt}在这个样例中,树的形态如下图所示。


def main():
    n, k = input().split()
    data_list = input().split()
    data_set = set(data_list)
    data_dict = {} for _ in range(int(n) - 1):
        i, j = input().split() if i not in data_dict.keys():
            data_dict[i] = [j] else:
            data_dict[i].append(j) if j not in data_dict.keys():
            data_dict[j] = [i] else:
            data_dict[j].append(i)
    min_cost = 0  result_num = 1  for data in data_list:
        edge_list = data_dict.get(data, [])
        edge_num = len(edge_list) - len(set(edge_list) & data_set) if edge_num > 0:
            result_num *= edge_num
            min_cost += 1   print(str(min_cost) + " " + str(result_num % (10 ** 9 + 7))) if __name__ == "__main__":
	
main()
# 我感觉我写赵帆这种也行,为啥通过不了,恳请大佬指教
发表于 2025-11-24 17:33:47 回复(0)
相连的被标记的结点为一组,最小的染色代价:组数,染色方案数量:每组 周围的未被标记的结点数 相乘
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int n = in.nextInt();
            int k = in.nextInt();
            List<Integer>[] tree = new ArrayList[n]; // 记录每个结点相连的 结点编号列表
            boolean[] selected = new boolean[n]; // 是否为被标记的结点,方便下面比较
            int[] selects = new int[k]; // 被标记的结点的编号,方便遍历
            boolean[] used = new boolean[n]; // 是否已被统计
            for (int i = 0; i < k; i ++) { // 被标记的结点
                int x = in.nextInt() - 1;
                selected[x] = true;
                selects[i] = x;
            }
            for (int i = 0; i < n - 1; i ++) {
                int x = in.nextInt() - 1;
                int y = in.nextInt() - 1; // x和y相连
                if (tree[x] == null){
                    List<Integer> list = new ArrayList<>();
                    tree[x] = list;
                }
                tree[x].add(y);
                if (tree[y] == null){
                    List<Integer> list = new ArrayList<>();
                    tree[y] = list;
                }
                tree[y].add(x);
            }

            int minCount = 0; // 最小的染色代价
            long type = 1; // 染色方案数量
            
            for (int i = 0; i < k; i ++) { // 遍历每个被标记的结点
                if (used[selects[i]])
                    continue; // 已在遍历前面的结点时被统计
                int white = 0; // 白边(周围未被标记的结点数)
                List<Integer> reds = new ArrayList<>(); // 一组红边(被标记的结点编号),0~n-1
                reds.add(selects[i]);
                for (int j = 0; j < reds.size(); j ++) { // 遍历每个 相连为一组的 被标记结点
                    int redIndex  = reds.get(j);
                    used[redIndex] = true;
                    for (int index : tree[redIndex]) { // 以每个被标记结点为中心,统计周围未被标记的结点数
                        if (!selected[index]) { // 白边
                            white ++;
                        } else if (!used[index]) { // 红边且未统计
                            used[index] = true;
                            reds.add(index); // 加入组中
                        }
                        
                    }   
                }
                if (white > 0) {
                    minCount ++; // 每有一组染色代价+1
                    type = (type * white) % 1000000007; // 每组数量相乘
                }
            }
            System.out.println(minCount + " " + type);
        }
    }
}


发表于 2025-08-05 22:15:38 回复(1)
import sys from collections import deque # 最小染色数量,为红线(两个标记点有相连)的数量 # 方案数量,是与红线有相连的未染色边的数量,并除以模MOD # 由于染色方案数量可能很大,请输出对10^9+7取模后的结果 MOD = 10 ** 9 + 7   def main():
    input_l = sys.stdin.read().split() # 输入列表,存的数字字符 ["1","2"]  ptr = 0 # 坐标指针  n, k = map(int, input_l[ptr:ptr + 2]) # 总节点数n(编号1~n), 标记的节点数k  ptr += 2 # 坐标指针后移2  marked = list(map(int, input_l[ptr:ptr + k])) # 读第二行输入的k个标记点的编号 [8,2,4,7,9,6]  ptr += k # 坐标指针后移k  marked_set = set(marked) # 生成集合,自动去重并按照默认升序排序{2, 4, 6, 7, 8, 9}   # 构建邻接表,n个表表示节点编号n的相邻点[[],[3,8,11],[],[1]]  adj = [[] for _ in range(n + 1)] # 构建邻接对所有边[(u0,v0),(u1,v1)]  edges = [] for _ in range(n - 1): # 读入节点连接情况  u, v = map(int, input_l[ptr:ptr + 2])
        ptr += 2  # 邻接表adju[]里加入节点v  adj[u].append(v) # 邻接表adjv[]里加入节点u  adj[v].append(u) # 邻接对edges加入  edges.append((u, v)) # 使用自环边找到标记节点的连通分量  parent = [0] * (n + 1) # [0,0,0]  # 访问标志表  visited = [False] * (n + 1) # [False,False,False]  # 大组件列表存所有红线小列表[[8,9][2,4][7,6]]  components = [] # 遍历所有已经标记的k个点的编号  for node in marked: # 如果没访问过  if not visited[node]:
            queue = deque() # 定义双端列表queue  # 将当前循环的已标记节点编号,从最右端加入双端列表  queue.append(node) # 将编号对应的内容置位,不会再重复访问  visited[node] = True  # 小组件列表,存每个红线[8,9]  component = [] # 双端列表非空时(有当前循环节点或其相邻节的1至多个点)  while queue: # 最左的点取出给u  u = queue.popleft() # u写入当前已标记节点的小组件列表  component.append(u) # 遍历第adj列表的第u个列表,vu所有相邻的节点编号  for v in adj[u]: # 如果该节点未被访问过,且v也是是被标记的节点,则两点连线为红线  # 访问并把父列表的v编号位置写为u  if not visited[v] and v in marked_set: # 检查边缘是否自动变红(两者都标记)  # 访问并把标志列表置位  visited[v] = True  # 将父列表的第v位写入编号u  parent[v] = u # 将该节点v加入双端列表最右侧,等待出栈  queue.append(v) # 将小组件加入大组件列表  components.append(component) # 组件编号表为[-1,-1,-1],节点存红边在components列表里的索引[-1,...,0,0,...](8~9)  component_id = [-1] * (n + 1) # 遍历大组件表,同时取组件列表的编号idx和值小列表comp  for idx, comp in enumerate(components): # 遍历当前小组件列表  for node in comp: # 将组件编号表的该节点对应的位置的改成小组件列表在大组件列表里的编号  component_id[node] = idx # 边界计数列表[2,0,0]编号第i的红线有n条相邻的黑边  boundary_counts = [0] * len(components) # 取出初始化的所有边的节点对  for u, v in edges: # uv中有1个未标记的节点  if (u in marked_set) != (v in marked_set): # 边界边缘  # 如果u已经标记  if u in marked_set: # 已标记点遍历写入u  marked_node = u # unmarked_node = v  # 如果v已经标记  else: # # 已标记点遍历写入v  marked_node = v # unmarked_node = u  # 取组件编号表里,该已标记节点位位对应的值  cid = component_id[marked_node] # 作为边界计数表的索引,并让对应值自增1  boundary_counts[cid] += 1  # 最小染色代价为红边数  min_cost = len(components) # 染色方案数初始化为1  num_ways = 1  # 遍历边界计数列表,染色方案数依次乘以每红边对应的黑边数边,并对10^9+7取模  for cnt in boundary_counts:
        num_ways = num_ways * cnt % MOD print(min_cost, num_ways) if __name__ == '__main__':
    main()
发表于 2025-07-16 01:22:44 回复(0)
import java.util.Scanner;
import java.util.Vector;

public class Main {
    static final int MOD = (int) (1e9 + 7);
    static final int MAXN = 200005;
    static final Boolean[] vis = new Boolean[MAXN];//访问的标记
    static final Boolean[] masked = new Boolean[MAXN];//特殊节点
    static final Vector<Vector<Integer>> G = new Vector<>();//图(邻接表)

    // 统计v能到达的非特殊节点的数量
    static int DFS(int v) {
        if (vis[v]) return 0;
        int vCnt = 0;//初始为零
        vis[v] = true;

        for (int i = 0; i < G.get(v).size(); i++) {
            if (masked[G.get(v).get(i)] && vis[G.get(v).get(i)] == false) {
                vCnt += DFS(G.get(v).get(i));//迭代新的特殊节点
            } else if (vis[G.get(v).get(i)] == false) {
                vCnt += 1;//连接的非特殊节点
            }
        }
        return vCnt;
    }

    public static void main(String[] args) {

        Scanner in = new Scanner(System.in);
        int n, k, u, v;
        n = in.nextInt();
        k = in.nextInt();

        //初始化特殊节点和节点访问标记
        for (int i = 0; i <= n; i++) {
            G.add(new Vector<>());
            vis[i] = false;
            masked[i] = false;
        }

        //填充特殊节点
        for (int i = 0, item = 0; i < k; i++) {
            item = in.nextInt();
            masked[item] = true;
        }

        //填充邻接表
        for (int i = 0; i < n - 1; i++) {
            u = in.nextInt();
            v = in.nextInt();
            G.get(u).add(v);
            G.get(v).add(u);
        }

        long ans = 1, cost = 0;//方案数量和代价
        for (int i = 1; i <= n; i++) {
            if (masked[i] && vis[i] == false) {
                int cnt = DFS(i);
                cost++;
                ans = (ans * cnt) % MOD;
            }
        }

        System.out.println(cost + " " + ans);
    }
}

发表于 2025-07-01 18:53:44 回复(0)
染色数量为标记区域数量,方案数量乘标记区域相连的未染色边
发表于 2025-03-22 00:00:44 回复(0)