1 条题解
-
0
「JOISC 2013 Day2」间谍 题解
有两家公司 JOI 和 IOI,各 名员工,形成树形结构(每名员工有唯一上司,总裁为根)。
两家公司各有 个项目,对应关系为:间谍项目 针对研究项目 。
- 项目成员:领导及其所有下属(子树中的所有节点)
- 间谍活动:员工 从 窃取信息。如果 属于 且 属于 ,则间谍成功。
求:每个 IOI 员工 在多少个间谍项目中成功完成间谍活动。
问题分析
关键约束
- 树结构:两家公司各自是一棵树
- 项目成员:领导节点的子树
- 间谍匹配: ↔ 一一对应
- 成功条件: 在 中 且 在 中
数据范围
解决方案
第一步:树形结构预处理
对两家公司的树分别做 DFS,得到每个节点的子树信息。
# DFS 预处理 def dfs(tree, node, tin, tout, timer): tin[node] = timer[0] timer[0] += 1 for child in tree[node]: dfs(tree, child, tin, tout, timer) tout[node] = timer[0] - 1得到:
- , : 在 JOI 树中的 DFS 序区间
- , : 在 IOI 树中的 DFS 序区间
重要性质:节点 是节点 的祖先 ⇔
第二步:问题转化
对于员工 ,间谍成功的项目 满足:
- 在 的子树中:
- 在 的子树中:
对于项目 ,可以看作二维平面上的点:
- 坐标:(间谍项目领导的 DFS 序)
- 坐标:(研究项目领导的 DFS 序)
对于员工 ,查询的是: 有多少个点 满足:
- (实际上需要更复杂的处理)
第三步:更精确的模型
实际上,条件 " 在 的子树中" 等价于:
- 是 的祖先(包括自身)
类似地," 在 的子树中" 等价于:
- 是 的祖先(包括自身)
因此,对于每个员工 ,我们需要统计项目 的数量,使得:
- 是 的祖先
- 是 的祖先
第四步:高效算法设计
方法1:祖先预处理 + 暴力匹配(适用于小数据)
# 预处理所有节点的祖先列表 def get_ancestors(parent, N): ancestors = [[] for _ in range(N+1)] for node in range(1, N+1): cur = node while cur != 0: ancestors[node].append(cur) cur = parent[cur] return ancestors # 对于每个员工a def count_for_employee(a, ioi_ancestors, joi_ancestors, projects): count = 0 # projects: list of (S_b, R_b) for S_b, R_b in projects: if S_b in ioi_ancestors[a] and R_b in joi_ancestors[a]: count += 1 return count复杂度:,对于 不可行。
方法2:基于 DFS 序的优化
关键观察: 设 表示 的所有祖先(包括自身)的集合。 设 表示 的所有祖先(包括自身)的集合。
我们需要统计:有多少个项目 满足 且 。
这可以看作一个二维计数问题。
优化思路:
- 对于每个间谍项目领导 ,收集所有以 为领导的项目对应的
- 对于每个员工 ,遍历 的所有祖先
- 对于每个祖先 ,检查 的祖先中包含了多少个
方法3:最终高效算法
算法步骤:
-
预处理:
- 对两家公司的树分别做 DFS,得到 DFS 序
- 预处理每个节点的所有祖先(或使用倍增法快速判断祖先关系)
-
项目索引:
- 创建数组
projects_by_leader[S]:存储所有以 为领导的间谍项目对应的
- 创建数组
-
员工计数: 对于每个员工 :
count = 0 for anc in ancestors_i[a]: # i_a的所有祖先 for R_b in projects_by_leader[anc]: if is_ancestor(R_b, j_a): # R_b是j_a的祖先 count += 1 -
祖先判断优化: 使用 DFS 序快速判断祖先关系:
def is_ancestor(u, v, tin, tout): return tin[u] <= tin[v] <= tout[u]
复杂度分析:
- 预处理: 存储所有祖先(, 可接受)
- 项目索引:
- 员工计数:最坏
由于 较小, 的操作是可接受的。
第五步:进一步优化
优化1:压缩项目信息
对于同一个领导 的多个项目,如果 相同,可以合并计数。
优化2:使用位集(bitset)
由于 ,可以使用 位的 bitset:
- 对于每个员工 ,用 bitset 表示它的所有祖先
- 对于每个间谍领导 ,用 bitset 表示所有相关的
然后对于员工 :
count = 0 for anc in ancestors_i[a]: count += (ancestors_j[a] & projects_bitset[anc]).count()复杂度:,其中 (机器字长)
优化3:离线处理 + 树状数组
更高级的算法:
- 将项目 看作二维点
- 对员工查询离线处理
- 使用树状数组进行二维数点
但 较小,bitset 方法更简单高效。
第六步:完整算法实现
import sys from collections import defaultdict def solve(): input = sys.stdin.read().split() idx = 0 N, M = int(input[idx]), int(input[idx+1]) idx += 2 # 读取员工信息 joi_parent = [0]*(N+1) ioi_parent = [0]*(N+1) joi_children = [[] for _ in range(N+1)] ioi_children = [[] for _ in range(N+1)] for a in range(1, N+1): P, Q = int(input[idx]), int(input[idx+1]) idx += 2 joi_parent[a] = P ioi_parent[a] = Q if P != 0: joi_children[P].append(a) if Q != 0: ioi_children[Q].append(a) # 读取项目信息 projects_by_leader = [[] for _ in range(N+1)] for _ in range(M): R, S = int(input[idx]), int(input[idx+1]) idx += 2 projects_by_leader[S].append(R) # DFS 预处理 def dfs(children, node, tin, tout, timer): tin[node] = timer[0] timer[0] += 1 for child in children[node]: dfs(children, child, tin, tout, timer) tout[node] = timer[0] - 1 # JOI 树 DFS 序 joi_tin = [0]*(N+1) joi_tout = [0]*(N+1) timer = [1] joi_root = [i for i in range(1, N+1) if joi_parent[i] == 0][0] dfs(joi_children, joi_root, joi_tin, joi_tout, timer) # IOI 树 DFS 序 ioi_tin = [0]*(N+1) ioi_tout = [0]*(N+1) timer = [1] ioi_root = [i for i in range(1, N+1) if ioi_parent[i] == 0][0] dfs(ioi_children, ioi_root, ioi_tin, ioi_tout, timer) # 祖先判断函数 def is_ancestor(u, v, tin, tout): return tin[u] <= tin[v] <= tout[u] # 预处理每个节点的所有祖先 joi_ancestors = [[] for _ in range(N+1)] ioi_ancestors = [[] for _ in range(N+1)] for a in range(1, N+1): # JOI 祖先 cur = a while cur != 0: joi_ancestors[a].append(cur) cur = joi_parent[cur] # IOI 祖先 cur = a while cur != 0: ioi_ancestors[a].append(cur) cur = ioi_parent[cur] # 为每个员工计算结果 results = [0]*(N+1) for a in range(1, N+1): count = 0 # 遍历 i_a 的所有祖先 for anc_i in ioi_ancestors[a]: # 检查以 anc_i 为领导的所有项目 for R_b in projects_by_leader[anc_i]: # 检查 R_b 是否是 j_a 的祖先 if is_ancestor(R_b, a, joi_tin, joi_tout): count += 1 results[a] = count # 输出结果 out = [] for a in range(1, N+1): out.append(str(results[a])) sys.stdout.write("\n".join(out)) if __name__ == "__main__": solve()第七步:复杂度分析
-
时间复杂度:
- DFS 预处理:
- 祖先预处理:
- 项目处理:
- 员工计数:最坏 ,但实际中平均项目数不会太大
- 对于 , 可接受
-
空间复杂度:
- 存储祖先列表:
- 存储项目:
算法标签
- 树结构:树形组织、DFS 序、祖先关系
- 图结构:两棵树、对应关系
- 数据结构:bitset、离线处理
- 搜索:DFS 遍历
- 模拟:模拟间谍活动匹配
总结
本题的核心是将间谍成功条件转化为树上的祖先关系查询:
- 利用 DFS 序快速判断祖先关系
- 预处理每个节点的所有祖先
- 对于每个员工,检查其 IOI 祖先领导的间谍项目对应的 JOI 领导是否是其 JOI 祖先
对于 的规模, 的算法是可行的。如果需要处理更大的 ,可以使用 bitset 优化或二维数点算法。
- 1
信息
- ID
- 5888
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者