NKOJ 3489 避难向导(LCA+倍增+DFS/DP)

P3489【2015多校联训5】避难向导

问题描述

“特大新闻,特大新闻!全国爆发了一种极其可怕的病毒,已经开始在各个城市中传播开来!全国陷入了巨大的危机!大量居民陷入恐慌,想要逃到其它城市以避难!经调查显示,该病毒来自于C 市的A 学校的一次非法的……”
“哎。”你关上电视,叹了口气。作为A 学校的校长,你一天前为了保命,独自逃离了A 学校,抛弃了全校师生,包括那个曾经帮你计算并拆除道路的工程师。
你良心受到了巨大的谴责,因此决定做出一些补救,回答一些逃难的人提出的询问。
已知该国一共有n 个城市,并且1 号城市是首都。(n-1)条双向的公路连接这些城市,通过这些公路,任意两个城市之间存在且仅存在一条路径。每条公路有一个长度。如果一个城市只与一条公路相连,则称它为边境城市。
该国政府有一个奇怪的规定:每个城市有一个封闭系数di,定义di 为离这个城市最远的边境城市到这个城市的距离。市民们认为,一个城市的安全系数Si 和它的封闭系数有很重要的联系。a,b,c 是该国的幸运数字,所以大家公认一个城市的安全系数Si = (di + a) * b mod c。
市民们一共会提出m 次询问。每个询问包含三个信息,xi,yi 和qi。xi 是询问者所在的城市编号。你要为这个询问者在xi 到yi 的必经之路上找出一个离xi最近的避难城市,并且要求这个避难城市的安全系数大于等于qi。如果存在这样的城市(包含xi 和yi),则输出城市编号,否则输出一行包括一个数-1。

输入格式

第一行五个数:依次是n, m, a, b, c。
接下来n-1 行描述公路的信息。每行三个数,前两个数代表这条公路连接的两个城市的编号,第三个数表示这条公路的长度。
再接下来m 行,每行描述一个询问,包含三个数xi, yi 和qi。

输出格式

对于每个询问,输出一行包含一个整数,存在符合要求的城市则输出城市编号,不存在则输出-1。

样例输入

7 6 5 6 20
1 2 4
2 4 2
2 5 3
1 3 5
3 6 6
6 7 7
7 5 15
3 4 5
5 4 2
4 5 2
6 6 10
3 5 19

样例输出

6
3
2
4
6
-1

数据范围

对于100%数据, 0 < xi, yi<=n; a,b,c,qi<=1,000,000;
注意:计算安全系数的中间过程可能需要使用64 位整数。


首先求安全系数,那么显然是找一条直径,然后到直径的某一个端点一定是最远距离,然而我居然写了个恶心的树dp

然后询问一条路径上,离起点最近的且安全系数大于 qi 的点,那么暴力枚举, n2 ,显然不行,需要优化。

考虑倍增, Maxdis[i][k] 表示 i 号节点的父亲开始往上 2k 个节点中,最大的安全系数,显然很好预处理。

查找时先找出 x y<

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LCA+路径压缩的方式可以用于求解树上的桥,具体实现步骤如下: 1. 对于树上每个节点,记录其在树中的深度(或者高度)以及其父亲节点。 2. 对于每个节点,记录其在树上的最小深度(或最小高度)以及其所在子树中深度最小的节点。 3. 对于每条边(u, v),设u的深度小于v的深度(或者高度),则如果v的子树中没有深度小于u的节点,则(u, v)是桥。 具体的实现过程如下: 首先,我们需要对树进行预处理,求出每个节点的深度以及其父亲节点。可以使用深度优先搜索(DFS)或广度优先搜索(BFS)来实现。在这里我们使用DFS来实现: ```c++ vector<int> adj[MAX_N]; // 树的邻接表 int n; // 树的节点数 int dep[MAX_N], fa[MAX_N]; // dep[i]表示节点i的深度,fa[i]表示节点i的父亲节点 void dfs(int u, int f, int d) { dep[u] = d; fa[u] = f; for (int v : adj[u]) { if (v != f) { dfs(v, u, d + 1); } } } ``` 接下来,我们需要计算每个节点所在子树中深度最小的节点。我们可以使用LCA(最近公共祖先)的方法来实现。具体来说,我们可以使用倍增算法来预处理出每个节点的2^k级祖先,并且在查询LCA时使用路径压缩的方式优化时间复杂度。这里我们不展开讲解LCA倍增算法的细节,如果你对此感兴趣,可以参考其他资料进行学习。 ```c++ const int MAX_LOG_N = 20; // log2(n)的上取整 int anc[MAX_N][MAX_LOG_N]; // anc[i][j]表示节点i的2^j级祖先 int mn[MAX_N]; // mn[i]表示节点i所在子树中深度最小的节点 void precompute() { // 预处理anc数组 for (int j = 1; j < MAX_LOG_N; j++) { for (int i = 1; i <= n; i++) { if (anc[i][j - 1] != -1) { anc[i][j] = anc[anc[i][j - 1]][j - 1]; } } } // 计算mn数组 for (int i = 1; i <= n; i++) { mn[i] = i; for (int j = 0; (1 << j) <= dep[i]; j++) { if ((dep[i] & (1 << j)) != 0) { mn[i] = min(mn[i], mn[anc[i][j]]); i = anc[i][j]; } } } } ``` 最后,我们可以使用LCA+路径压缩的方式来判断每条边是否为桥。具体来说,对于每条边(u, v),我们需要判断v的子树中是否存在深度小于u的节点。如果存在,则(u, v)不是桥,否则(u, v)是桥。 ```c++ bool is_bridge(int u, int v) { if (dep[u] > dep[v]) swap(u, v); if (mn[v] != u) return true; // 子树中存在深度小于u的节点 return false; // 子树中不存在深度小于u的节点 } ``` 完整代码如下:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值