题目链接

题目

题目描述

一个国家有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. Linux 查看office文件及pdf文件

    1.查看pdf文件 evince PdfFile_name 查看office文件 openoffice.org 文件名 & // 打开或者编辑.doc.odt等文本文档命令 openoffic ...

  2. IDEA:端口号被占用解决办法

    idea遇到这样的问题:如下图 解决办法 步骤1:通过端口号找到pid打开dos命令行,输入netstat -ano | find "9009"得到下列内容,看到最后一行就是pid ...

  3. VIte+Vue3 打包在本地 双击 index.html 打开项目

    npm i @vitejs/plugin-legacy --save import legacy from '@vitejs/plugin-legacy'; export default define ...

  4. Qt5.9 UI设计(三)——添加UI、类及资源文件

    前言 设计一个软件,最简单的方式就是把控件直接往UI上放,然后再把功能实现了.这样可以实现基本的功能,但是界面不能缩放,如果拖动软件改变界面的大小,界面上的控件就会乱成一团,或者是界面的控件压根就不会 ...

  5. 部分MySQL的SQL信息整理

    模块补丁信息查看 select su as 补丁模块, count(1) as 数量 from gsppatchlog where TO_DAYS( NOW( ) ) - TO_DAYS(deploy ...

  6. [转帖]Linux内核参数 rp_filter

    https://www.cnblogs.com/chenmh/p/6001977.html 简介 rp_filter (Reverse Path Filtering)参数定义了网卡对接收到的数据包进行 ...

  7. [转帖]Oracle性能优化-大内存页配置

    一.为什么需要大页面? 如果您有一个大的RAM和SGA,那么HugePages对于Linux上更快的Oracle数据库性能是至关重要的.如果您的组合数据库SGAs很大(比如超过8GB,甚至对于更小的数 ...

  8. Python学习之二:不同数据库相同表是否相同的比较方法

    摘要 昨天学习了使用python进行数据库主键异常的查看. 当时想我们有跨数据库的数据同步场景. 对应的我可以对不同数据库的相同表的核心字段进行对比. 这样的话能够极大的提高工作效率. 我之前写过很长 ...

  9. [转帖]exportfs命令

    https://www.cnblogs.com/xzlive/p/9766388.html exportfs命令:功能说明 :NFS共享管理语法格式exportfs [必要参数][选择参数][目录]功 ...

  10. [转帖]一个故事看懂CPU的SIMD技术

    https://www.cnblogs.com/xuanyuan/p/16048303.html 好久不见,我叫阿Q,是CPU一号车间的员工.我所在的CPU有8个车间,也就是8个核心,咱们每个核心都可 ...