【JAVA】连通分量中的背包问题(Pro20210712)(BFS + DP)

【JAVA】连通分量中的背包问题(Pro20210712)(BFS + DP)

题目

S 公司的网络管理员琳收到了一通紧急来电。来电告知公司内部设备连接的网络中发现了恶意代码,必须马上采取相应措施。
S 公司有 N 台设备,通过 M 根线路连接,所以如果其中一台设备感染恶意代码,其他设备也会通过连接网络感染上恶意代码。目前,已有 K 台设备感染恶意代码,代码扩散危机迫在眉睫。受感染的设备需要立即处理,但在目前的情况下,最多只能处理三台设备。
保受感染的设备数量尽可能少的情况下,帮助琳确定要处理的设备,同时求出未受感染设备的最大数量。

在这里插入图片描述

如上图所示,有 10 台设备连接,编号为 2、4、5、8 的设备感染了恶意代码,处理编号为 2、4、5 的设备,就可以保护 7 台设备,这是能实现的不受感染设备的最大数量。

[限制条件]
1.设备数量N为5以上,100,000以下的整数。
2.线路数量 M 为1以上,300,000以下的整数。
3.受感染的设备数K为3以上,N以下的整数。
4.受感染的设备即使经过处理后,仍然可能会再次被其他受感染的设备感染。

[输入]
首先给出测试用例数量T,随后给出T个测试用例。在每个测试用例的第一行,给定设备数量 N,线路数量 M、感染恶意代码的设备数量 K,以空格分隔。从第二行到第M行,每行给定一条线路两端连接的设备 A 和 B 的编号,以空格分隔。在第 M+2 行,给定 K 台感染恶意代码的设备编号,以空格分隔。

[输出]
每行输出一个测试用例。首先,输出“#x”(其中 x 表示测试用例编号,从 1 开始),加一个空格,最后输出处理三台设备后,恶性代码扩散时,能实现的未感染设备的最大数量。

[输入和输出示例]
(输入)
2
10 9 4
1 2
2 3
2 5
4 5
4 6
5 6
5 7
8 9
9 10
2 4 5 8
10 9 5
1 2
2 3
2 5
4 5
4 6
5 6
5 7
8 9
9 10
2 4 5 6 8

(输出)
#1 7
#2 3

思路

求出每个连通分量的点个数以及坏点个数。
然后用零一背包求满足坏点个数的连通分量点个数和的最大值(0个坏点的,是必须选的)。

代码

import java.io.*;
import java.util.*;

public class Main {
	static int N, M, K;
	static ArrayList<Integer>[] DATA; // 链表数组,用于记录每个点的直接连接点
	static boolean[] VISITED; // 已访问标记数组
	static boolean[] IS_BAD; // 坏点记录数组
	static ArrayList<int[]> DP_DATA; // 背包使用数组
	static long ASW;
	static int CNT; // 已访问点的数量

	public static void main(String[] args) throws IOException {
		System.setIn(new FileInputStream("D:\\SW\\TestCase\\sample_input_20210712.txt"));
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

		StringTokenizer st = new StringTokenizer(br.readLine());

		int T = Integer.parseInt(st.nextToken()); // Case数量
		for (int t = 1; t <= T; t++) {
			st = new StringTokenizer(br.readLine());
			N = Integer.parseInt(st.nextToken()); // 点数量
			M = Integer.parseInt(st.nextToken()); // 边数量
			K = Integer.parseInt(st.nextToken()); // 坏点数量

			DATA = new ArrayList[N + 1]; // 初始化链表数组,用于记录每个点的直接连接点
			VISITED = new boolean[N + 1]; // 初始化已访问标记数组
			IS_BAD = new boolean[N + 1]; // 初始化坏点记录数组
			DP_DATA = new ArrayList<int[]>(); // 初始化背包使用数组
			ASW = 0; // 初始化最终结果
			CNT = 0; // 初始化已访问点的数量

			for (int i = 1; i <= N; i++) {
				DATA[i] = new ArrayList<Integer>(); // 初始化每个点的连接点
			}

			Arrays.fill(VISITED, false);
			Arrays.fill(IS_BAD, false);

			// 读入边(连接)信息,并记录
			for (int i = 0; i < M; i++) {
				st = new StringTokenizer(br.readLine());
				int s = Integer.parseInt(st.nextToken());
				int e = Integer.parseInt(st.nextToken());
				DATA[s].add(e);
				DATA[e].add(s);
			}

			// 读入坏点信息,并记录
			st = new StringTokenizer(br.readLine());
			for (int i = 0; i < K; i++) {
				int tmp = Integer.parseInt(st.nextToken());
				IS_BAD[tmp] = true;
			}

			// BFS求出每个连通分量节点及其中的坏点个数
			for (int i = 1; i <= N; i++) {
				if (CNT == N) // 若每个点都已经被访问过,则所有连通分量已经求出
					break;

				if (VISITED[i]) // 若当前点被访问过,则剪枝
					continue;

				bfs(i); // 当前点尚未被访问到过,则新发现一个联通分量,BFS求连通分量节点及其中的坏点个数
			}

			// 方案一:零一背包求最优解
			getPackage();

			// 方案二:
			// 最多只能修复3个坏点,“3”这个数字是固定的
			// 那么只有三种情况:
			// 3 = 1 + 1 + 1,3 = 1 + 2,3 = 3
			// 也就是说,有0个坏点的,是必须计算的(Sum(有0个坏点的点数量最多的3个连通分量))
			// 再加上另外的三种情况取最大值;
			// 即:
			// 情况1(3 = 1 + 1 + 1):Sum(只有一个坏点的点数量最多的3个连通分量)
			// 情况2(3 = 1 + 2):Sum(只有一个坏点的点数量最多的1个连通分量,有两个坏点的点数量最多的1个连通分量)
			// 情况3(3 = 3):Sum(有三个坏点的点数量最多的1个连通分量)

			bw.write("#" + t + " " + ASW + "\n");
			bw.flush();
		}
		bw.close();
	}

	static void getPackage() {
		int[] dp = new int[4];

		for (int i = 0; i < DP_DATA.size(); i++) {
			int[] tmp = DP_DATA.get(i);
			for (int j = 3; j >= tmp[0]; j--) {
				dp[j] = Math.max(dp[j - tmp[0]] + tmp[1], dp[j]);
			}
		}

		ASW = dp[3];
	}

	static void bfs(int s) {
		Queue<Integer> pq = new ArrayDeque<Integer>();
		pq.add(s);

		VISITED[s] = true;

		int tmpCnt = 0; // 当前连通分量节点数量
		int tmp = 0; // 当前连通分量坏点数量
		while (!pq.isEmpty()) {
			int curr = pq.poll();
			tmpCnt += 1;
			CNT++;

			if (IS_BAD[curr])
				tmp += 1;

			for (int i = 0; i < DATA[curr].size(); i++) {
				int next = DATA[curr].get(i);

				if (VISITED[next])
					continue;

				VISITED[next] = true;
				pq.add(next);
			}
		}

		// 1.如果tmp>3,则此连通图的顶点个数为0
		// 2.如果tmp<=3,则最大联通图的顶点为tmp<=3,tmp为0 1 2 3 的加总取最大
		if (tmp <= 3) {
			DP_DATA.add(new int[] { tmp, tmpCnt });
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值