题目链接

题目

题目描述

一个国家有n个城市,有n-1条道路连接,保证联通。还有m条铁路,从1~m编号,第i条铁路是从ui到vi的简单路径,多次询问一段区间的铁路的车站。

一个点可以作为区间[L,R]铁路的车站满足以下条件:

1、R-L+1条铁路都经过这个车站。

2、R-L+1条铁路经过的所有城市中,离车站最远的城市,与它的距离最小。如果有多个,那么选择编号较小的。

并且存在铁路发生改变的情况。

输入描述

第一行两个整数n,m,表示城市的个数和铁路条数。

接下来n-1行,每行两个整数,描述一条道路。

接下来m行,每行两个整数,描述一条铁路(注意道路与铁路的区分)。

然后一行一个整数Q,表示询问个数。

接下来Q行,每行3个或者4个整数,描述一次操作,具体如下:

操作1:1,l,r,表示询问[l,r]铁路的车站。

操作2:2,x,u,v,表示第x条铁路变成从u到v的一条简单路径。

\(n,m,Q \leq10^5, \ \ u,v\leq n, \ \ \ l,r\leq m\)

输出描述

对于每次询问操作,输出一个整数,表示车站的编号。如果没有满足条件的城市可以作为车站,输出-1。

示例1

输入

6 3
1 2
1 3
2 4
2 5
4 6
4 5
2 3
1 3
7
1 1 1
1 1 2
1 1 3
2 3 1 6
1 2 3
2 1 1 4
1 1 2

输出

2
2
-1
2
1

题解

知识点:树链剖分,LCA,线段树。

考虑用线段树维护铁路的路线交路径 \((u,v)\),在 \(u\) 外侧距离 \(u\) 最远的车站距离 \(du\) ,在 \(v\) 外侧距离 \(v\) 最远的车站距离 \(dv\) 。

铁路合并时,我们需要求出路径交,运用到一个黑科技:

  1. 俩俩组合两条路径的端点求LCA,得到 \(4\) 个LCA 。
  2. 取深度最大的两个LCA。
  3. 若相同且深度大于某条路径的LCA,则交路径为空集,否则即为交路径的两个端点。

然后,我们需要求出新的 \(du,dv\) 。显然,新的最远车站一定从旧的两组最远车站得到,因此我们分别求出两条路径原先的最远车站到交路径端点的距离,这个可以直接从最远距离维护。其中,需要注意交路径的方向不一定和原先路径方向一致,交路径端点对应到最远车站更近的一侧。

更新非常容易,直接修改对应节点信息即可。

查询某个区间的铁路时,先求出这组铁路的交路径和最远车站距离,而最优车站一定是总距离的中点,我们分类讨论:

  1. 交路径为空集,输出 \(-1\) 。
  2. 否则,若中点出现在 \(u,v\) 上及其外侧,那么直接取 \(u,v\) 即可。注意这里中点不能总距离除 \(2\) 得到,因为会出现 \(u,v\) 同时是中点,但 \(u\) 编号更大却选了 \(u\) 的情况。当然也可以在判断前将编号小的换到 \(u\) 上。
  3. 否则,中点在 \((u,v)\) 内,我们需要找到这个中点,取 \(mid\) 为总距离除以 \(2\) 取下整,按总距离奇偶讨论:
    1. 总距离为奇数,则有唯一确定的中点,直接找 \((u,v)\) 路径上距离 \(u\) 为 \(mid - du\) 的点。
    2. 否则,有两个中点, \((u,v)\) 路径上距离 \(u\) 为 \(mid - du\) 的点和距离 \(u\) 为 \(mid+1 - du\) 的点,取编号最小的那个。

时间复杂度 \(O(n + m \log m + q \log m \log n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long; struct HLD {
vector<int> siz, dep, fat, son, top, dfn, L, R; HLD() {}
HLD(int rt, const vector<vector<int>> &g) { init(rt, g); } void init(int rt, const vector<vector<int>> &g) {
assert(g.size() >= 2);
int n = g.size() - 1;
siz.assign(n + 1, 0);
dep.assign(n + 1, 0);
fat.assign(n + 1, 0);
son.assign(n + 1, 0);
top.assign(n + 1, 0);
dfn.assign(n + 1, 0);
L.assign(n + 1, 0);
R.assign(n + 1, 0); function<void(int, int)> dfsA = [&](int u, int fa) {
siz[u] = 1;
dep[u] = dep[fa] + 1;
fat[u] = fa;
for (auto v : g[u]) {
if (v == fa) continue;
dfsA(v, u);
siz[u] += siz[v];
if (siz[v] > siz[son[u]]) son[u] = v;
}
};
dfsA(rt, 0); int dfncnt = 0;
function<void(int, int)> dfsB = [&](int u, int tp) {
top[u] = tp;
dfn[++dfncnt] = u;
L[u] = dfncnt;
if (son[u]) dfsB(son[u], tp);
for (auto v : g[u]) {
if (v == fat[u] || v == son[u]) continue;
dfsB(v, v);
}
R[u] = dfncnt;
};
dfsB(rt, rt);
} int LCA(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
u = fat[top[u]];
}
return dep[u] < dep[v] ? u : v;
} int dist(int u, int v) { return dep[u] + dep[v] - 2 * dep[LCA(u, v)]; } int jump(int u, int k) {
if (dep[u] < k) return -1;
int d = dep[u] - k;
while (dep[top[u]] > d) u = fat[top[u]];
return dfn[L[u] - dep[u] + d];
} bool isAncester(int u, int v) { return L[u] <= L[v] && L[v] <= R[u]; } pair<int, int> path_intersection(pair<int, int> A, pair<int, int> B) {
auto [u1, v1] = A;
auto [u2, v2] = B;
vector<int> lca = { LCA(u1,u2),LCA(u1,v2),LCA(v1,u2),LCA(v1,v2) };
sort(lca.begin(), lca.end(), [&](int a, int b) {return dep[a] > dep[b];});
int p1 = lca[0], p2 = lca[1];
if (p1 == p2 && (dep[p1] < dep[LCA(u1, v1)] || dep[p1] < dep[LCA(u2, v2)])) return { -1,-1 };
else return { p1,p2 };
}
}; template<class T, class F>
class SegmentTree {
int n;
vector<T> node; void update(int rt, int l, int r, int x, F f) {
if (r < x || x < l) return;
if (l == r) return node[rt] = f(node[rt]), void();
int mid = l + r >> 1;
update(rt << 1, l, mid, x, f);
update(rt << 1 | 1, mid + 1, r, x, f);
node[rt] = node[rt << 1] + node[rt << 1 | 1];
} T query(int rt, int l, int r, int x, int y) {
if (r < x || y < l) return T();
if (x <= l && r <= y) return node[rt];
int mid = l + r >> 1;
return query(rt << 1, l, mid, x, y) + query(rt << 1 | 1, mid + 1, r, x, y);
} public:
SegmentTree(int _n = 0) { init(_n); }
SegmentTree(const vector<T> &src) { init(src); } void init(int _n) {
n = _n;
node.assign(n << 2, T());
}
void init(const vector<T> &src) {
assert(src.size() >= 2);
init(src.size() - 1);
function<void(int, int, int)> build = [&](int rt, int l, int r) {
if (l == r) return node[rt] = src[l], void();
int mid = l + r >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
node[rt] = node[rt << 1] + node[rt << 1 | 1];
};
build(1, 1, n);
} void update(int x, F f) { update(1, 1, n, x, f); } T query(int x, int y) { return query(1, 1, n, x, y); }
};
const int N = 1e5 + 7;
vector<int> g[N];
HLD hld; struct T {
int u = -1, v = -1;
int du = -1, dv = -1;
friend T operator+(const T &a, const T &b) {
if (a.u == -1) return b;
if (b.u == -1) return a;
if (a.u == 0 || b.u == 0) return { 0,0,0,0 }; auto [p1, p2] = hld.path_intersection({ a.u, a.v }, { b.u,b.v });
if (p1 == -1) return { 0,0,0,0 }; T ans = T();
ans.u = p1;
ans.v = p2; vector<int> dis;
dis = { hld.dist(a.u, ans.u),hld.dist(a.u, ans.v),hld.dist(a.v, ans.u),hld.dist(a.v, ans.v) };
if (dis[0] < dis[1]) {
ans.du = max(ans.du, a.du + dis[0]);
ans.dv = max(ans.dv, a.dv + dis[3]);
}
else {
ans.du = max(ans.du, a.dv + dis[2]);
ans.dv = max(ans.dv, a.du + dis[1]);
}
dis = { hld.dist(b.u, ans.u),hld.dist(b.u, ans.v),hld.dist(b.v, ans.u),hld.dist(b.v, ans.v) };
if (dis[0] < dis[1]) {
ans.du = max(ans.du, b.du + dis[0]);
ans.dv = max(ans.dv, b.dv + dis[3]);
}
else {
ans.du = max(ans.du, b.dv + dis[2]);
ans.dv = max(ans.dv, b.du + dis[1]);
} return ans;
}
};
struct F {
int u, v;
T operator()(const T &x) { return { u,v,0,0 }; }
}; SegmentTree<T, F> sgt; int find(int u, int v, int k) {
int lca = hld.LCA(u, v);
if (hld.dist(u, lca) < k) return hld.jump(v, hld.dist(u, v) - k);
else return hld.jump(u, k);
} int query(int l, int r) {
auto [u, v, du, dv] = sgt.query(l, r);
if (u == 0) return -1;
int d = hld.dist(u, v);
//! 下面不能用mid取下整判断,否则会出现两个(u,v)内中点,但只能选标号最小的那个,就会判断错误
if (du >= d + dv) return u; // 中点(如果有两个,则会取到右侧中点)落在u及其左侧
else if (dv >= d + du) return v; // 中点(如果有两个,则会取到左侧中点)落在v及其右侧
int mid = du + d + dv >> 1;
if ((du + d + dv) & 1) return min(find(u, v, mid - du), find(u, v, mid + 1 - du));
else return find(u, v, mid - du);
} int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
for (int i = 1;i <= n - 1;i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
hld.init(1, vector<vector<int>>(g, g + n + 1)); vector<T> src(m + 1);
for (int i = 1;i <= m;i++) {
int u, v;
cin >> u >> v;
src[i] = { u,v,0,0 };
}
sgt.init(src); int q;
cin >> q;
while (q--) {
int op;
cin >> op;
if (op == 1) {
int l, r;
cin >> l >> r;
cout << query(l, r) << '\n';
}
else {
int x, u, v;
cin >> x >> u >> v;
sgt.update(x, { u,v });
}
}
return 0;
}

NC22544 车站的更多相关文章

  1. 洛谷 洛谷 P1011 车站 Label:续命模拟QAQ 未知50分

    题目描述 火车从始发站(称为第1站)开出,在始发站上车的人数为a,然后到达第2站,在第2站有人上.下车,但上.下车的人数相同,因此在第2站开出时(即在到达第3站之前)车上的人数保持为a人.从第3站起( ...

  2. 洛谷P1983 车站分级

    P1983 车站分级 297通过 1.1K提交 题目提供者该用户不存在 标签图论贪心NOIp普及组2013 难度普及/提高- 提交该题 讨论 题解 记录 最新讨论 求帮忙指出问题! 我这么和(diao ...

  3. 洛谷 P1983 车站分级

    题目链接 https://www.luogu.org/problemnew/show/P1983 题目描述 一条单向的铁路线上,依次有编号为 1,2,…,n的 n个火车站.每个火车站都有一个级别,最低 ...

  4. python爬虫之12306网站--车站信息查询

    python爬虫查询车站信息 目录: 1.找到要查询的url 2.对信息进行分析 3.对信息进行处理 python爬虫查询全拼相同的车站 目录: 1.找到要查询的url 2.对信息进行分析 3.对信息 ...

  5. 洛谷P1983车站分级题解

    题目 这个题非常毒瘤,只要还是体现在其思维难度上,因为要停留的车站的等级一定要大于不停留的车站的等级,因此我们可以从不停留的车站向停留的车站进行连边,然后从入度为0的点即不停留的点全都入队,然后拓扑排 ...

  6. 【NowCoder368E】车站(线段树)

    [NowCoder368E]车站(线段树) 题面 牛客网 题解 链交的结果显然和求解的顺序无关,因此我们可以拿线段树维护区间链的链交结果. 然后怎么求解最远点. 维护链交的时候再记录两个点表示到达链交 ...

  7. 3.用Thead子类及Runnable接口类实现车站购票的一个场景(static关键字)

    如上图所示,我们这里模拟一下去车站买票的情形:这里有3个柜台同时售票,总共是1000张票,这三个柜台同时买票,但是只能一个柜台卖同一张票,也就是说1号票卖了之后我们就只能买2号票,2号票卖了之后我们只 ...

  8. 车站(NOIP1998)

    题目链接:车站 这一题,首先你要会推导,推到出式子后,就会像我一样简单AC. 给一张图: 这里,t是第二个车站上车人数. 有什么规律? 其实很好找.有如下规律: 第x车站的人数增量为第x-2车站的上车 ...

  9. luogu P1011 车站

    题目描述 火车从始发站(称为第1站)开出,在始发站上车的人数为a,然后到达第2站,在第2站有人上.下车,但上.下车的人数相同,因此在第2站开出时(即在到达第3站之前)车上的人数保持为a人.从第3站起( ...

  10. P1011 车站

    P1011 车站 题目描述 火车从始发站(称为第1站)开出,在始发站上车的人数为a,然后到达第2站,在第2站有人上.下车,但上.下车的人数相同,因此在第2站开出时(即在到达第3站之前)车上的人数保持为 ...

随机推荐

  1. Tmux | 常用操作存档

    (因为自己实在是太好忘了,所以在博客存档方便查找) 参考资料:Tmux 使用教程 | 阮一峰的网络日志 tmux new -s <session-name> <Ctrl+B D> ...

  2. 如何在Python中的子进程获取键盘输入

    场景:在Python中使用multiprocessing模块的Process创建子进程,试图在子进程中获取键盘输入. 使用input() 在子进程中使用input()会弹出报错信息:EOFError: ...

  3. SpringBoot实现限流注解

    SpringBoot实现限流注解 在高并发系统中,保护系统的三种方式分别为:缓存,降级和限流. 限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统,一旦达到限制速率 ...

  4. [转帖]MySQL 8.0 以后的版本策略变化

    https://www.modb.pro/db/1717815842220630016 产品版本变更   从2023年7月18日开始,MySQL官网出现了一个新的版本 MySQL 8.1.0,直接改变 ...

  5. [转帖]windows10彻底关闭Windows Defender的4种方法

    https://zhuanlan.zhihu.com/p/495107049 Windows Defender是windows10系统自带的杀毒软件.默认情况下它处于打开的状态.大多数第三方的杀毒软件 ...

  6. [转帖]043、TiDB特性_缓存表和分区表

    针对于优化器在索引存在时依然使⽤全表扫描的情况下,使⽤缓存表和分区表是提升查询性能的有效⼿段. 缓存表 缓存表是将表的内容完全缓存到 TiDB Server 的内存中 表的数据量不⼤,⼏乎不更改 读取 ...

  7. 【转帖】千亿参数大模型首次被撬开!Meta复刻GPT-3“背刺”OpenAI,完整模型权重及训练代码全公布

    https://cloud.tencent.com/developer/article/1991011 千亿级参数AI大模型,竟然真的能获取代码了?! 一觉醒来,AI圈发生了一件轰动的事情-- Met ...

  8. [转帖]spec2017 安装和使用

    https://zhuanlan.zhihu.com/p/534205632 SPEC成立于1988年,SPEC基准广泛用于评估计算机系统的性能.SPEC CPU套件通过测量几个程序(例如编译器GCC ...

  9. Vue3中ref和toRef的区别

    1. ref是复制,视图会更新 如果利用ref将某一个对象中的某一个属性值变成响应式数据 我们修改响应式数据是不会影响原始数据的; 同时视图会跟新. ref就是复制 复制是不会影响原始数据的 < ...

  10. axios发送请求时携带token

    请求头携带token async getUserlist(){ // 需要授权的Api,必须在青丘头中使用Authorization 字段提供token令牌 const AUTH_TOKEN=loca ...