|
|
一、 题目大意
给定一棵无根树(由 $N$ 个房间和 $N-1$ 条带权通道组成),以及三个道具位置 $A$、$B$、$C$。目标是找到一个汇合房间 $X$,使得 $A$、$B$、$C$ 到 $X$ 的路径长度之和最小。若有多个 $X$,输出房间编号字典序最小者(即编号最小)。 5 3 1 4 3 5 5 4 3 9 4 1 7 1 2 1 - 参数解析:$N=5$(5 个房间),$A=3$(某样道具在房间 3),$B=1$(某样道具在房间 1),$C=4$(某样道具在房间 4)。 - 树结构:边表示房间间通道及长度,形成树: - 房间 1 和 2 连接,长度 1。 - 房间 1 和 4 连接,长度 7。 - 房间 3 和 4 连接,长度 9。 - 房间 3 和 5 连接,长度 5。 - 等效路径:房间2 —(1)— 房间1 —(7)— 房间4 —(9)— 房间3 —(5)— 房间5。
距离计算: - 到 $A=3$ 的距离: - 房间 3: 0 - 房间 4: 9(直连边) - 房间 1: 9 + 7 = 16 - 房间 2: 16 + 1 = 17 - 房间 5: 5(直连边) - 到 $B=1$ 的距离: - 房间 1: 0 - 房间 2: 1 - 房间 4: 7 - 房间 3: 7 + 9 = 16 - 房间 5: 16 + 5 = 21 - 到 $C=4$ 的距离: - 房间 4: 0 - 房间 1: 7 - 房间 3: 9 - 房间 2: 7 + 1 = 8 - 房间 5: 9 + 5 = 14 |room i| dis[i][A] | dis[i][B] | dis[i][C] |SUM_dis | |------|-----------|-----------|-----------|--------| | 1 | 16 | 0 | 7 | 23 | | 2 | 17 | 1 | 8 | 26 | | 3 | 0 | 16 | 9 | 25 | | 4 | 9 | 7 | 0 | 16 | | 5 | 5 | 21 | 14 | 40 | 输出分析:最小距离和为 $16$,在房间 $4$ 汇合(无其他房间相同和)。因此输出房间编号 $4$ 和距离和 $16$,符合样例输出。 二、 思路解析第一层:解题基石
邻接表存树 + BFS - 树的性质利用:由于树中任意两点间路径唯一且无环,从单点出发到所有其他点的最短路径可通过一次 $BFS$ 或 $DFS$ 计算(边权为正,无需 $Dijkstra$)。 - 关键观察:最小距离和的点一定在 $A$、$B$、$C$ 三点形成的子树中,但为简化实现,可直接计算所有房间到 $A$、$B$、$C$ 的距离(时间复杂度 $O(N)$,$N \leq 20,000$ 可接受)。
第二层:思维脉络
1. 从 $A$ 点执行 $BFS/DFS$,计算每个房间到 $A$ 的距离,存储数组 $distA$。 2. 同理,从 $B$ 点计算 $distB$,从 $C$ 点计算 $distC$。 3. 遍历所有房间 $i$(从 $1$ 到 $N$),计算总距离和 $S_i = \text{distA}[i] + \text{distB}[i] + \text{distC}[i]$。 4. 找到 $S_i$ 最小的房间,若有多个,选 $i$ 最小者。
第三层:难点与陷阱
距离和访问标记数组的初始化和更新、$BFS$ 多次调用前的环境参数初始化处理是易错点; 起初想建图,跑最短路算法,后发现是树形结构,房间之间路径唯一,且通过遍历可以在 $O(N)$ 时间内即可求得到其他所有点的距离,故选 $BFS$。 三、 代码实现
#include<bits/stdc++.h>
using namespace std;
const int N = ___; //房间数上限
const int MX = ___; //无穷大边界
//可以用struct或pair定义边
vector<___> graph[___]; // 邻接表存储树:边{v, w} 存入graph[u]
int disA[___], disB[___], disC[___]; // 分别存储到A、B、C的距离
int n, a, b, c;
int visited[___]; //访问标记数组
// BFS计算单源最短路径(树是无环的,BFS足够)
void bfs(int start, int dis[]) {//由于多次调用BFS,故把disA[],...B[],...C[]直接传给函数中的dis[]
//Bfs函数中数组的传递方式,其核心是传递数组的首地址(即指针),这使得在函数内部可以直接修改主函数中数组的值。
memset(___, ___, ___); //初始化 dis[]
memset(___, ___, ___); //初始化 visited[]
dis[start] = ___; //起点距离初始化
___ //定义队列q
___//起点入队 且 标记起点已访问
while (___) {//当队列不空
___ //取出队头u
for (___) {//枚举graph[u]中 u 的每一个邻接点 v
int v = ___; //邻接点
int w = ___; //边权
//如果 v 未访问
dis[v] = ___; //更新dis[v]
___ //标记 v 已访问 且 入队
}
}
}
int main() {
freopen("___.___", "___", ___);
freopen("___.___", "___", ___);
// 读取输入
cin >> n >> a >> b >> c; //房间数,三人所在房间ABC
for (int i = 1; i < ___; i++) {
int u, v, w;
//输入边,并建图
}
// 计算从A、B、C出发到所有点的距离
bfs(a, disA); //bfs计算A到其他点的距离
bfs(b, ___); //亦然
bfs(c, ___); //亦然
// 寻找最优汇合点
int bestRoom = ___; //最佳房间号初始化
int minSum = ___; //最短距离之和初始化
for (int i = 1; i <= n; i++) {//枚举所有房间,寻找最佳汇合点
___
}
// 输出结果
cout << ___ << ___ << ___ << endl;
return 0;
}
$O(N)$(三次 $BFS/DFS$ 加一次遍历)。 $O(N)$(三个距离数组和三次 $BFS$ 遍历使用的队列)。 四、 总结与反思
通过本题,我深刻理解了树结构在算法设计中的优越性。由于树的特殊性质(任意两点间路径唯一、无环),许多在图论中复杂的问题在树上可以简化为高效的线性或近似线性算法。这提醒我在解决实际问题时,首先要分析数据结构的特性,充分利用特定结构的性质来优化算法设计。特别是 $BFS/DFS$ 在树上的应用,相比通用图的最短路径算法(如 $Dijkstra$)更加高效简洁。 本题可以作为图论入门向进阶过渡的典型题目。它比简单的单源最短路径问题复杂,但又比网络流、强连通分量等高级图论算法简单,非常适合用来训练选手对图论基础知识的综合运用能力,如 $BFS/DFS$ 遍历、邻接表存储、距离计算等核心技能。 本题可以迁移到信息学奥赛中常见的"$树上多源最短路径$"类题目。这类题目通常给定一棵树和多个关键点,要求找到一个点使得所有关键点到该点的距离之和最小或最大。类似的题目包括寻找 $树的中心$、$重心$等 概念相关的问题,都需要计算树上多点间的距离关系。 该问题与运用 $LCA$ 技术解决的题目高度相关。在信息学奥赛中,很多树上路径问题都需要借助 $LCA$ 来高效计算两点间距离。虽然本题可以通过三次 $BFS$ 解决,但对于更大的数据规模或更复杂的需求,通常会使用 $LCA$ 加树上差分等高级技巧来优化计算过程。 五、 推荐题目
题目1241 [NOIP 2010冲刺十三]逃离遗迹
1
评论
2025-10-30 14:03:06
|
|
|
Pro4184 轻重数字 题解前言 由 zlc 和 fhx 提供的思路,orz。 貌似是 COGS 目前的最优解?(可能是其他人的常数太大了) 思路分析 先考虑一个经典的 dp,设 $f_i$ 表示将 $1\sim i$ 划分为若干好数组的方案,则有转移 $f_i=\sum f_{j-1}$,其中满足 $[j,i]$ 是一个好区间。 这样枚举 $i$,枚举 $j$,$O(n)$ 检测,就有 $O(n^3)$ 的 dp 了。注意到 $n\le 5\times 10^5$,考虑优化。 不难发现好的子数组在原数组中,要么是奇数位置为轻元素,偶数位置为重元素;要么是偶数位置为轻元素,奇数位置为重元素,所以我们分两种情况转移:分别是以 $i$ 结尾,奇数位置为轻元素和 $i$ 结尾,偶数位置为轻元素。两者本质没有区别,我们依靠前者讨论。 我们考虑区间左端点 $j$ 可以取到哪些位置,考虑对于任意 $x\in [1,i]$,元素 $x$ 对左端点 $j$ 的约束。 1. 若 $x$ 为奇数位置,作为轻元素约束 $j$。 则 $j$ 必须满足 $[lst_x+1,n]$,即对于任意 $i\in [1,n]$,只要 $j\in [lst_x+1,n]$,区间 $[j,i]$ 就可以满足 $x$ 的限制。 证明不难,当 $j\in [lst_x+1,x]$ 时,区间至多包含一个 $x$。当 $j>x$,区间一个 $x$ 都没有,自然无法约束。 容易发现,对 $j$ 的限制个数是奇数位置所有位置。 2.若 $x$ 为偶数位置,作为重元素约束 $j$ 这种情况较为复杂。 首先可以发现对 $j$ 限制个数是元素种类数,对于同一种元素,我们用这种元素在 $[1,i]$ 中最后一次出现的位置来约束 $j$。 记 $p$ 为上一个在奇数位置出现的 $x$,$q$ 为 $x$ 上一次出现的位置。当 $j\le q$ 时,$[i,j]$ 一定包含了 $q,x$,满足重元素的限制,当 $j\le p$ 时,$p$ 作为重元素出现在奇数位置,$[i,j]$ 不合法,所以 $x$ 的限制是 $j\in [q+1,p]\cup[x+1,n]$。 注意:此时是按照 $x$ 是 $[1,i]$ 最后一次出现的数,随着 $i$ 的右移,如果在偶数位置出现一个和 $x$ 一样的元素 $y$。就要先撤销 $x$ 的限制,然后加入 $y$ 的限制。 考虑如何维护满足限制的端点:维护每个位置满足限制的个数,不难维护出限制的总个数,对于一个位置,如果它足一个限制,就让他的标记数组加一。如果一个位置满足的限制个数为总个数,就说明 $[j,i]$ 是一个合法的端点。 注意一种特殊情况是 $[i,i]$ 是合法区间,我们只统计 $j\in [1,i-1]$ 的合法左端点。 直接用数组模拟是 $O(n^2)$,容易用线段树优化到 $O(n\log n)$,具体的记录每个区间任意位置满足限制个数最大值,满足限制个数达到这个最大值的 $f_{i-1}$ 之和,pushup 分讨一下即可。 然后这个问题就解决了。
题目4184 轻重数字
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1
评论
2025-10-29 17:16:34
|
|
|
先创建一个 $map$,使 $m[队列元素]=$队列编号,这样后面好查找元素的组别; 再创建一个队列 $q[301]$,$q[i]$ 表示队列编号为 $i$ 的排队情况。$q[0]$ 表示小组的排序; 队列输入不用多说,只需要注意一个新小组的人入队要给新小组排上队; 队列输出时查看 $q[0]$ 队头,看到小组编号后查该小组的队头,再输出,注意小组的人全部出队后要把空小组删掉。
题目717 [SDOI 2007] 小组队列
AAAAAAAAAA
3
评论
2025-10-29 12:55:32
|
|
|
Pro4184 轻重数字 题解【题解】算法一:思路:动态规划,$dp[i]$ 是前 $i$ 个元素的有效划分方案数,则 $f[0]=1$(空数组有 $1$ 种划分方式),$f[i]$ 为所有满足 $[j+1,i]$ 是好数组的 $f[j]$ 的和。 时间复杂度:$O(n^2)$,即 $5000*5000=2.5*10^7$,期望 $15$ 分。 瓶颈:不能快速判断数组是否是好数组,不能快速累加 $f[j]$; 算法二:思路:有几处优化; $1$.对每个元素 $a[i]$,计算 $pre[i]$(上一次出现位置)和 $nxt[i]$(下一次出现位置),所以可以由好数组的性质“对于子数组内的重元素 $a[i]$,其前后出现位置必须在子数组外(否则会影响交替性)”优化判断好数组的条件; $2$.若 $j$ 在 $[L..R]$ 内时 $[j+1..i]$ 是好数组,则 $f[i]$ 需要累加 $f[L]+f[L+1]+...+f[R]$,所以用双线段树。好数组有两种起始类型(轻元素开始或重元素开始),需分别维护。因此使用两个线段树 $seg[0]$ 和 $seg[1]$,分别对应两种起始类型,每个线段树节点存储“区间内最大有效长度”和“对应方案数总和”支持区间增减(通过延迟标记)和单点更新; $3$.预先生成“区间更新事件”:当遍历到位置 $i$ 时,哪些 $j$ 的范围会因 $a[i]$ 的加入而成为有效划分点,事件按生效位置排序,遍历数组时依次触发,动态更新线段树,确保查询到的 $f[j]$ 都是有效的。 同样方式计算 $f[i]$,最终需减去重复计算的 $f[i-1]$,对 $1000003$ 取余后输出。 时间复杂度:$O(n*logn)$,即 $5*10^5*log5*10^5≈10^7$,轻松过 $4$ 秒时限; 期望得分:$100$;
题目4184 轻重数字
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3
评论
2025-10-29 12:53:24
|
|
|
Pro4184 轻重数字 题解[CCO 2024] Heavy Light Decomposition
前置知识
分块,DP
简要题意
定义“好的数组”为一个数组内交替出现“轻元素”和“重元素”,轻元素即其在这个数组内是唯一的,重元素即其在数组内出现多次。 有 $n$ 个正整数 $a_i$,求其有多少种划分方案能使划分后的子数组均为好的数组。
分析样例
我们首先要搞懂一个东西,就是划分后好的数组,是指这个子数组是好的,在这个子数组内的轻重元素与原数组并没有关系,每个子数组是互相独立的。
对于样例一,其划分方案如下:
- $[1], [2], [3], [2], [3]$ - $[1], [2, 3, 2], [3]$ - $[1], [2], [3, 2, 3]$ - $[1, 2, 3, 2], [3]$
对于样例二,其划分方案如下: - $[1], [2], [1], [3], [1]$ - $[1, 2, 1], [3], [1]$ - $[1, 2, 1, 3], [1]$ - $[1], [2], [1, 3, 1]$ - $[1], [2, 1, 3, 1]$ - $[1, 2, 1, 3, 1]$
不明白的建议手推一下。
思路分析
考虑转移
我们定义 $dp[i]$ 为前 $i$ 个元素的合法划分的方案数。
那么,转移方程很显然:$dp[i] = \sum dp[j]$,其中 $j < i$ 且 子数组 $[j + 1, i]$ 是好数组。
实际上含义就是在 $j$ 处划分,新增一个子数组 $[j + 1, i]$,方案累加前 $j$ 个元素的方案数。
考虑好数组的约束
如果 $[j + 1, i]$ 为好的数组,那么需要满足:
1. 类型交替:即数组内的元素轻重交替。 2. 奇偶性约束:如果重元素第一次出现在奇数位,那么奇数位全是重元素,反之。
因此我们如果直接枚举所有的 $j$ 去验证 $[j + 1, i]$ 是否为好数组,时间复杂度为 $O(n ^ 2)$。
考虑优化
对于当前的位置 $i$,我们设其元素大小为 $v$,用 $odd[v]$ 和 $even[v]$ 来记录 $v$ 在奇数和偶数位最近的出现位置,这样的话可以确定 $j$ 的下界。
为了保证子数组 $[j + 1, i]$ 满足类型交替,需要避免 $v$ 元素在数组内出现奇偶性冲突,那么若 $j + 1 < \min(odd[v], even[v])$ 的话,则会使其冲突。
因此我们使 $minL$ 取所有元素 $min(odd[v], even[v]) + 1$ 的最大值,因此 $j > minL - 1$。
然后是最重要的分块,我们将原数组分块,每个块维护两个核心内容,$sum[k][b]$ 表示 $b$ 块满足在奇偶性 $k$ 下的合法的 $dp[j]$ 之和,$kpos[k][b]$ 是在 $k$ 的奇偶性下块 $b$ 是否满足。另外,为了维护分块时的单个元素, 我们维护 $pos[k][i]$ 是单个位置的 $i$ 是否满足奇偶性 $k$。
当我们查询 $[l, r]$ 内符合条件的 $dp[j]$ 之和时,对于整块,只需要判断 $kpos$ 是否有效,然后累加 $sum$ 即可,对于单个的块边缘的元素,则需要满足 $kpos$ 和 $pos$,有效则累加 $dp[j]$。
在区间更新时,只需要标记区间有效和无效,在完整的块上更新 $kpos$,零散的元素更新 $pos$ 和 $sum$。
时间复杂度
分块的单次查询和更新的时间复杂度为 $O(\sqrt{n})$,时间复杂度为 $O(n \sqrt n)$。
简单卡常即可,最慢的点才两秒出头,对于四秒的时间限制完全够用。
题目4184 轻重数字
4
2 条 评论
2025-10-28 21:45:35
|
|
|
![]()
题目4076 小b爱旅行
4
评论
2025-10-24 11:19:14
|