小明是一名网络工程师,他负责管理公司数据中心里的一台或多台核心交换机。
-
初始配置 (
configs
): 当他开始一天的工作时,这台交换机上已经存在一些配置好的"专线"(Dedicated Lines)。这里的专线可以理解为为某个重要客户(比如一个银行)或某个关键业务(比如在线支付)预留的、点对点的专用网络通道。例如[2, 3]
就表示交换机的 2 号端口和 3 号端口之间已经建立了一条专线,这两个端口目前正被这项业务占用。这个初始状态就是configs
数组。 -
配置请求 (
batchReqs
): 在一天的工作中,他会收到来自不同部门的各种工作单(Work Orders),这些就是batchReqs
。["a", 0, 1]
(Add): "市场部新上了一个在线活动网站,需要一条新的专线连接服务器,请占用 0 号和 1 号端口。" 作为工程师,小明首先要检查 0 号和 1 号端口是不是空闲的,如果是,就在配置计划中加上这条新线。如果任何一个端口已经被占用了,这个请求就无法完成。["d", 4, 5]
(Delete): "财务部的那个旧服务器要下线了,请拆除连接 4 号和 5 号端口的专线。" 小明需要确认这条专线确实存在,然后才能在配置计划中将它移除,并释放这两个端口。
-
批量提交与命令简化: 直接登录到昂贵且关键的核心设备上频繁地敲命令,既耗时又容易出错。因此,现代网管软件允许小明先在软件界面上完成所有计划的添加和删除操作。 所有操作都确认无误后,小明点击"提交"按钮。此时,软件并不会把小明刚才的每一步操作都原样发给设备。它会智能地对比设备的初始状态 (
configs
) 和你操作完成后的最终目标状态 ,然后计算出一个最短的、最有效的命令列表来完成这个状态变更。- 例如,小明先
add [0,1]
,后来又delete [0,1]
。对于最终结果来说,这条线没有变化,所以软件就不应该向设备发送任何关于[0,1]
的命令。
- 例如,小明先
现在我们将问题抽象一下。
给定:
- 一个初始集合 (Initial Set)
S_initial
,其元素是无序数对{a, b}
。 - 一个操作序列 (Operation Sequence)
Ops
,其中每个操作是(type, {x, y})
,type
为"添加"或"删除"。
任务:
-
模拟状态演变 (Simulate State Evolution):
-
从一个当前集合 (Current Set)
S_current
开始,初始时S_current = S_initial
。 -
维护一个已使用元素集合 (Used Elements Set)
U
,初始时包含S_initial
中所有数对里的所有元素。 -
依次处理
Ops
中的每一个操作(type, {x, y})
:- 如果
type
是添加 :当且仅当元素x
和y
都未 出现在U
中时,将{x, y}
加入S_current
,并将x
和y
加入U
。 - 如果
type
是删除 :当且仅当{x, y}
存在于S_current
中时,从S_current
中移除{x, y}
,并从U
中移除x
和y
。
- 如果
-
所有操作处理完毕后,得到一个最终集合 (Final Set)
S_final
(即演变后的S_current
)。
-
-
计算集合差异 (Calculate Set Difference):
- 我们的目标是生成一组最少的命令,将
S_initial
变换为S_final
。这组命令就是这两个集合的对称差 (Symmetric Difference) 。 - 需要删除的元素集合:
Deletions = S_initial - S_final
(即,在初始集合中存在,但在最终集合中不存在的元素)。 - 需要添加的元素集合:
Additions = S_final - S_initial
(即,在最终集合中存在,但在初始集合中不存在的元素)。
- 我们的目标是生成一组最少的命令,将
-
格式化输出 (Format Output):
- 将
Deletions
集合中的每个数对{a, b}
格式化为["d", a, b]
。 - 将
Additions
集合中的每个数对{a, b}
格式化为["a", a, b]
。 - 对两组命令分别按数对的第一个元素升序排序。
- 最终返回一个列表,其中包含所有排好序的删除命令,后面跟着所有排好序的添加命令。
- 将
java
import java.util.*;
import java.util.stream.Collectors;
class SpecializedLineConfigurator {
/**
* 内部辅助类,用于规范化表示一个专线连接。
* 通过在构造函数中排序,确保 (id1, id2) 和 (id2, id1) 被视为等价。
*/
private static class ConnectionPair {
final int id1;
final int id2;
ConnectionPair(int id1, int id2) {
// 始终将较小的 ID 放在前面,作为规范表示
this.id1 = Math.min(id1, id2);
this.id2 = Math.max(id1, id2);
}
// 重写 equals 和 hashCode 方法,是让 HashSet 能正确工作的关键
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ConnectionPair that = (ConnectionPair) o;
return id1 == that.id1 && id2 == that.id2;
}
@Override
public int hashCode() {
return Objects.hash(id1, id2);
}
}
/**
* 主逻辑方法,处理所有操作并返回简化后的命令列表。
* @param configs 初始配置
* @param batchReqs 批量配置请求
* @return 经过简化的、需要下发到设备的命令列表
*/
public List<List<Object>> getSimplifiedCommands(int[][] configs, Object[][] batchReqs) {
// --- 1. 初始化 ---
Set<ConnectionPair> initialState = new HashSet<>();
Set<Integer> occupiedPorts = new HashSet<>();
// 从 configs 初始化 initialState 和 occupiedPorts
for (int[] config : configs) {
ConnectionPair pair = new ConnectionPair(config[0], config[1]);
initialState.add(pair);
occupiedPorts.add(config[0]);
occupiedPorts.add(config[1]);
}
// currentState 从 initialState 的一个副本开始
Set<ConnectionPair> currentState = new HashSet<>(initialState);
// --- 2. 模拟批量请求,更新网管侧的当前配置 ---
for (Object[] req : batchReqs) {
String opType = (String) req[0];
int id1 = (Integer) req[1];
int id2 = (Integer) req[2];
ConnectionPair pair = new ConnectionPair(id1, id2);
if ("a".equals(opType)) {
// 如果两个端口都未被占用,则添加成功
if (!occupiedPorts.contains(id1) && !occupiedPorts.contains(id2)) {
currentState.add(pair);
occupiedPorts.add(id1);
occupiedPorts.add(id2);
}
} else if ("d".equals(opType)) {
// 如果专线存在,则删除成功
if (currentState.contains(pair)) {
currentState.remove(pair);
occupiedPorts.remove(id1);
occupiedPorts.remove(id2);
}
}
}
// --- 3. 计算配置差异 ---
List<List<Object>> deleteCommands = new ArrayList<>();
// 初始状态有,但当前状态没有 -> 需要删除
for (ConnectionPair pair : initialState) {
if (!currentState.contains(pair)) {
deleteCommands.add(Arrays.asList("d", pair.id1, pair.id2));
}
}
List<List<Object>> addCommands = new ArrayList<>();
// 当前状态有,但初始状态没有 -> 需要添加
for (ConnectionPair pair : currentState) {
if (!initialState.contains(pair)) {
addCommands.add(Arrays.asList("a", pair.id1, pair.id2));
}
}
// --- 4. 排序并合并命令列表 ---
// 按 id1 升序对删除命令排序
deleteCommands.sort(Comparator.comparingInt(cmd -> (int) cmd.get(1)));
// 按 id1 升序对添加命令排序
addCommands.sort(Comparator.comparingInt(cmd -> (int) cmd.get(1)));
// 先添加所有删除命令,再添加所有添加命令
List<List<Object>> finalCommands = new ArrayList<>();
finalCommands.addAll(deleteCommands);
finalCommands.addAll(addCommands);
return finalCommands;
}
}
public class Main {
public static void main(String[] args) {
SpecializedLineConfigurator solver = new SpecializedLineConfigurator();
// 样例 1
System.out.println("--- 样例 1 ---");
int[][] configs1 = {{2, 3}, {4, 5}};
Object[][] batchReqs1 = {{"a", 0, 1}, {"a", 5, 6}, {"d", 0, 1}, {"d", 1, 2}, {"a", 0, 6}, {"d", 4, 5}, {"a", 1, 4}};
List<List<Object>> result1 = solver.getSimplifiedCommands(configs1, batchReqs1);
// 使用 stream 和 map 来美化输出,使其看起来像 [[d, 4, 5], [a, 0, 6], [a, 1, 4]]
System.out.println(result1.stream().map(Arrays::toString).collect(Collectors.toList()));
System.out.println();
// 样例 2
System.out.println("--- 样例 2 ---");
int[][] configs2 = {};
Object[][] batchReqs2 = {{"a", 10, 20}, {"a", 50, 60}, {"d", 50, 60}, {"d", 10, 20}};
List<List<Object>> result2 = solver.getSimplifiedCommands(configs2, batchReqs2);
System.out.println(result2.stream().map(Arrays::toString).collect(Collectors.toList()));
}
}