[BalticOI 2019 Day1] 山谷
原题链接
题目大意:
一颗
每次询问第
1.若能走到根节点,则输出 escaped。
2.若走不到根节点也走不到任意个特殊点,则输出 oo。
3.若走不到根节点但能走到特殊点,则输出最近特殊点的距离。
解题思路:
先给张图,方便理解(图很丑,看懂就行 QWQ)
我们设深度大的那个点为
第一种情况输出 escaped :
那就说明
第二种情况输出 oo :
到不了根节点且到不了特殊点,说明
第三种情况:
到不了根节点,但能到特殊点。
找的特殊点的范围一定在
暴力 DFS 找复杂度是
注意到
其中
我们可以设
最终答案即为
易错 or 代码中难理解的点(☆)
- 开 long long。
- 找最近特殊点必须只找子树内的,找外面的如果边被断开就没办法了,倍增的话就倍增到
就行了。或者其实那个子树外的特殊点是合法的,但那下次往上跳的时候一定会统计到的。 - 当
时,答案是不会更新的,所以手动判断一下。
代码实现(细节有点多,放注释里了,非常仔细的,看一看吧 QWQ):
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int Num = 1e5 + 5;
int n, s, q, e;
int go[Num][20], dep[Num], u[Num], v[Num], shop_num[Num];
// shop_num[i] 表示i及i的子树内有多少商店
ll c[Num], near_shop[Num], f[Num][20], p[Num]; //这几个都要开long long!!!
//!!!只用统计子树内的最近商店,子树外的以后会统计到或根本用不到
bool mark[Num];
struct edge {
int to;
ll w;
};
vector<edge> g[Num];
inline ll read() {
ll s = 0, f = 1; char c = getchar();
while (c < '0' || c>'9') { if (c == '-')f = -1; c = getchar(); }
while (c >= '0' && c <= '9') { s = s * 10 + c - '0'; c = getchar(); }
return s * f;
}
void dfs_pretreat(int x, int fa) { //pretreat v.预处理
go[x][0] = fa; dep[x] = dep[fa] + 1;
if (mark[x])shop_num[x]++;
for (int i = 0; i < g[x].size(); i++) {
int t = g[x][i].to;
if (t == fa)continue;
c[t] = c[x] + g[x][i].w;
dfs_pretreat(t, x);
shop_num[x] += shop_num[t];
}
}
void dfs_shop(int x, int fa) {
if (mark[x])near_shop[x] = 0;
for (int i = 0; i < g[x].size(); i++) {
int t = g[x][i].to;
if (t == fa)continue;
dfs_shop(t, x);
near_shop[x] = min(near_shop[x], near_shop[t] + g[x][i].w);
//只找子树内的最近商店
}
}
int lca(int x, int y) {
if (dep[x] < dep[y])swap(x, y); //让x成为深度大的点
for (int i = 18; i >= 0; i--)if (dep[go[x][i]] >= dep[y])x = go[x][i];
for (int i = 18; i >= 0; i--)if (go[x][i] != go[y][i])x = go[x][i], y = go[y][i];
return (x == y) ? x : go[x][0];
}
void solve(int I, int R) {
if (dep[u[I]] < dep[v[I]])swap(u[I], v[I]);
int point = u[I];
int LCA = lca(R, point);
if (LCA != point) { //escaped 优先级大于 oo
cout << "escaped\n";
return;
}
if (LCA == point && shop_num[point] == 0) {
cout << "oo\n";
return;
}
ll ans = (ll)0x3f3f3f3f3f3f, pos = R;
for (int i = 18; i >= 0; i--) {
if (dep[go[pos][i]] >= dep[point]) {
ans = min(ans, f[pos][i]); //先更新再跳
pos = go[pos][i];
}
}
if (point == R)ans = near_shop[R] - c[R]; //往上跳不了时
cout << ans + c[R] << endl;
}
int main() {
n = read(), s = read(), q = read(), e = read(); //e是根节点
for (int i = 1; i < n; i++) {
u[i] = read(), v[i] = read();
int w = read();
g[u[i]].push_back(edge{ v[i],w });
g[v[i]].push_back(edge{ u[i],w });
}
for (int i = 1; i <= s; i++)mark[read()] = 1;
dfs_pretreat(e, e); //e是根节点
memset(near_shop, 0x3f, sizeof(near_shop)); //要设为无穷大!!!
dfs_shop(e, e); //!!!只用统计子树内的最近商店,子树外的以后会统计到
for (int t = 1; t <= 18; t++)
for (int i = 1; i <= n; i++)
go[i][t] = go[go[i][t - 1]][t - 1];
for (int i = 1; i <= n; i++)p[i] = near_shop[i] - c[i];
//p临时存一下,因为f[i][t]需要表示走2^t步
for (int i = 1; i <= n; i++)f[i][0] = min(p[i],p[go[i][0]]);
//如果i为商店,f[i][0]不需要置为0!!!
for (int t = 1; t <= 18; t++)
for (int i = 1; i <= n; i++)
f[i][t] = min(f[i][t - 1], f[go[i][t - 1]][t - 1]);
//找c[R] + min{ near_shop[i] - c[i] } i∈子树 v[I]
//f[i][j] 表示在 i ~ go[i][j] 这些点中, (near_shop[]-c[]) 最小是多少(典型倍增思想),最后加上c[R]就为最终answer
while (q--) {
int a = read(), b = read();
solve(a, b);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本