【做题】51Nod1766树上的最远点对——直径&线段树
原文链接 https://www.cnblogs.com/cly-none/p/9890837.html
题意:给出一棵大小为\(n\)的树,边有边权。\(m\)次询问,每次给出两个标号区间\([a,b]\)和\([c,d]\),求\(\max {dis(i,j) \ | \ a \leq i \leq b, \, c \leq j \leq d }\)。
\(n,m \leq 10^5\)
本题主要是对直径性质的运用。
先考虑这样一个结论。
对于两个点集\(A\)和\(B\),如果\(A\)的最远点对是\((a,b)\),\(B\)的最远点对是\((c,d)\),那么,点集\(A \bigcup B\)的最远点对的两个点一定是\(a,b,c,d\)中的两个。
至于证明,可以考虑直径的性质:到任何一个结点的最远点一定是两个直径端点中的一个。
注:一个点集的最远点对可以通过构建虚树转化为直径。
那么,考虑\(A\)中的一个结点,在\(B\)集合,到它最远的结点一定可以是\(c,d\)中的一个。否则,我们可以反证\((c,d)\)不是\(B\)中的最远点对。那么,在\(A \bigcup B\)中,任何一个结点,到它的最远点一定是\(a,b,c,d\)中的一个。因此,最远点对就一定是\(a,b,c,d\)中的两个。
于是,我们可以用线段树维护区间的最远点对。再根据我们的结论,\([c,d]\)中到\([a,b]\)的任意一点最远的结点一定是\([c,d]\)里最远点对中的一点,因此我们求出\([a,b]\)和\([c,d]\)各自的最远点对,就能求出答案了。
时间复杂度\(O(n \log n)\)。
#include <bits/stdc++.h>
using namespace std;
#define gc() getchar()
template <typename tp>
inline void read(tp& x) {
x = 0; char tmp; bool key = 0;
for (tmp = gc() ; !isdigit(tmp) ; tmp = gc())
key = (tmp == '-');
for ( ; isdigit(tmp) ; tmp = gc())
x = (x << 3) + (x << 1) + tmp - '0';
if (key) x = -x;
}
const int N = 100010, MP = 19;
struct edge {
int la,b,v;
} con[N << 1];
int tot,fir[N],n,m;
void add(int from,int to,int val) {
con[++tot] = (edge) {fir[from],to,val};
fir[from] = tot;
}
int dep[N],dfn[N << 1],dcnt,mn[N << 1][MP],ln[N << 1],rec[N],dis[N];
int lca(int x,int y) {
x = rec[x], y = rec[y];
if (x > y) swap(x,y);
int len = ln[y - x + 1];
return dep[dfn[mn[y][len]]] < dep[dfn[mn[x + (1 << len) - 1][len]]] ?
dfn[mn[y][len]] : dfn[mn[x + (1 << len) - 1][len]];
}
void dfs(int pos,int fa) {
dep[pos] = dep[fa] + 1;
dfn[rec[pos] = ++dcnt] = pos;
for (int i = fir[pos] ; i ; i = con[i].la) {
if (con[i].b == fa) continue;
dis[con[i].b] = dis[pos] + con[i].v;
dfs(con[i].b,pos);
dfn[++dcnt] = pos;
}
}
typedef pair<int,int> pii;
pii t[N << 2];
int ask(int x,int y) {
return dis[x] + dis[y] - 2 * dis[lca(x,y)];
}
void merge(pii& x,pii ls,pii rs) {
static int rec[4];
rec[0] = ls.first;
rec[1] = ls.second;
rec[2] = rs.first;
rec[3] = rs.second;
x = pii(-1,-1);
int cur = -1, tmp;
for (int i = 0 ; i < 4 ; ++ i)
for (int j = i+1 ; j < 4 ; ++ j) {
if (rec[i] == -1 || rec[j] == -1) continue;
tmp = ask(rec[i],rec[j]);
if (tmp > cur) x = pii(rec[i],rec[j]), cur = tmp;
}
}
void build(int x=1,int lp=1,int rp=n) {
if (lp == rp)
return (void) (t[x] = pii(lp,-1));
int mid = (lp + rp) >> 1;
build(x<<1,lp,mid);
build(x<<1|1,mid+1,rp);
merge(t[x],t[x<<1],t[x<<1|1]);
}
pii query(int l,int r,int x=1,int lp=1,int rp=n) {
if (l > rp || lp > r) return pii(-1,-1);
if (lp >= l && rp <= r)
return t[x];
int mid = (lp + rp) >> 1;
pii ret;
merge(ret,query(l,r,x<<1,lp,mid),query(l,r,x<<1|1,mid+1,rp));
return ret;
}
int main() {
int x,y,z,a,b,c,d;
read(n);
for (int i = 1 ; i < n ; ++ i) {
read(x), read(y), read(z);
add(x,y,z);
add(y,x,z);
}
dfs(1,0);
for (int i = 1 ; i <= dcnt ; ++ i) {
mn[i][0] = i;
for (int j = 1 ; (1 << j) <= i ; ++ j)
mn[i][j] = dep[dfn[mn[i][j-1]]] < dep[dfn[mn[i - (1 << j >> 1)][j-1]]] ?
mn[i][j-1] : mn[i - (1 << j >> 1)][j-1];
}
for (int i = 2 ; i <= dcnt ; i <<= 1)
++ ln[i];
for (int i = 2 ; i <= dcnt ; ++ i)
ln[i] += ln[i-1];
build();
read(m);
for (int i = 1 ; i <= m ; ++ i) {
read(a), read(b), read(c), read(d);
pii t1 = query(a,b);
pii t2 = query(c,d);
printf("%d\n",max(max(ask(t1.first,t2.first),ask(t1.first,t2.second)),max(ask(t1.second,t2.first),ask(t1.second,t2.second))));
}
return 0;
}
小结:直径这个东西的性质还是很丰富的。通过利用点集直径的可合并性,很容易套上一些数据结构。同时,两个点集间的最远点对可以转化为求各自的直径,这就使回答多个集合间的最远点对异常简洁。
【做题】51Nod1766树上的最远点对——直径&线段树的更多相关文章
- 【51NOD1766】树上的最远点对(线段树,LCA,RMQ)
题意:n个点被n-1条边连接成了一颗树,给出a~b和c~d两个区间, 表示点的标号请你求出两个区间内各选一点之间的最大距离,即你需要求出max{dis(i,j) |a<=i<=b,c< ...
- 51nod 1766 树上的最远点对(线段树)
像树的直径一样,两个集合的最长路也是由两个集合内部的最长路的两个端点组成的,于是我们知道了两个集合的最长路,枚举一下两两端点算出答案就可以合并了,所以就可以用线段树维护一个区间里的最长路了. #inc ...
- 51Nod1766 树上的最远点对
1766 树上的最远点对 n个点被n-1条边连接成了一颗树,给出a~b和c~d两个区间,表示点的标号请你求出两个区间内各选一点之间的最大距离,即你需要求出max{dis(i,j) |a<=i&l ...
- 51Nod1766 树上的最远点对 ST表 LCA 线段树
原文链接https://www.cnblogs.com/zhouzhendong/p/51Nod1766.html 题目传送门 - 51Nod1766 题意 n个点被n-1条边连接成了一颗树,给出a~ ...
- BZOJ 3038: 上帝造题的七分钟2 / BZOJ 3211: 花神游历各国 (线段树区间开平方)
题意 给出一些数,有两种操作.(1)将区间内每一个数开方(2)查询每一段区间的和 分析 普通的线段树保留修改+开方优化.可以知道当一个数为0或1时,无论开方几次,答案仍然相同.所以设置flag=1变表 ...
- bzoj 4034 [HAOI2015]树上操作 入栈出栈序+线段树 / 树剖 维护到根距离和
题目大意 有一棵点数为 N 的树,以点 1 为根,且树点有边权.然后有 M 个 操作,分为三种: 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子树中所有点的点权都 ...
- 树上第k小,可持久化线段树+倍增lca
给定一颗树,树的每个结点都有权值, 有q个询问,每个询问是 u v k ,表示u到v路径上第k小的权值是多少. 每个结点所表示的线段树,是父亲结点的线段树添加该结点的权值之后形成的新的线段树 c[ro ...
- 2015 UESTC 数据结构专题D题 秋实大哥与战争 变化版本的线段树,合并区间,单点查询
D - 秋实大哥与战争 Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://acm.uestc.edu.cn/#/contest/show/59 D ...
- DP 优化方法大杂烩 & 做题记录 I.
标 * 的是推荐阅读的部分 / 做的题目. 1. 动态 DP(DDP)算法简介 动态动态规划. 以 P4719 为例讲一讲 ddp: 1.1. 树剖解法 如果没有修改操作,那么可以设计出 DP 方案 ...
随机推荐
- Java课程寒假之《人月神话》有感之一
一.焦油坑 以前上课的时候,老师讲过早期的程序由于工作量不大,大多只需要几个人完成,随着软件规模的不断扩大,代码量直线上升,仅仅一两个人可能没有办法完成这样的任务,多以开始形成了团队的规模,焦油坑说的 ...
- 【Swing/文本组件】定义自动换行的文本域
文本域组件:Swing中任何一个文本域(JTextArea)都是JTestArea类型的对象.常用的构造方法如下 public JTextArea() public JTextArea(String ...
- F#周报2019年第5期
新闻 Ionide的Open Collective 发布SAFE至Google Cloud Kubernetes引擎 .NET Core 3预览2 ASP.NET Core在.NET Core 3预览 ...
- css学习_css精灵技术、字体图标
1.精灵技术产生的背景(减少向服务器请求的次数,减小服务器压力) 2.精灵技术的本质(小的背景图集中在一张大图上) 3.精灵技术的使用 案例1: 案例2: 注意:产品类的图片一般不是用背景,而是用im ...
- ES6 Reflect 与 Proxy
概述 Proxy 与 Reflect 是 ES6 为了操作对象引入的 API . Proxy 可以对目标对象的读取.函数调用等操作进行拦截,然后进行操作处理.它不直接操作对象,而是像代理模式,通过对象 ...
- release 步骤
一:在新的 TR 里面创建自己的task 1: /n se01 进入自己的用户名下面 2:display 3:选中自己的名字右键>check object 4:选中自己的名字右键> dir ...
- OGG学习笔记05-OGG的版本
刚接触OGG的时候,很容易被众多的版本搞晕,虽然官方有提供各版本对应认证OS和DB的表格. 个人认为一个比较简单的方式,是直接去edelivery.oracle.com下载OGG,选定一个大版本后,这 ...
- Linux. 计划任务 时间格式
Linux. 计划任务 时间格式 在linux中执行指令:cat /etc/crontab 结果,如下图所示: 结果一目了然,不多说. 如有问题,欢迎纠正!!! 如有转载,请标明源处:https:// ...
- SQL 增加列、修改列、删除列
SQL语句增加列.修改列.删除列 1.增加列: alter table tableName add columnName varchar(30) 2.1. 修改列类型: alter table tab ...
- [iOS] 测试设备解决自签名证书问题
不多说,解决过程都是泪. 用了最简单粗暴的方式. 1. 将你的自签名证书,放到测试设备可以访问的站点上 2. 用safari访问上面的地址,直接将证书安装到本设备上 搞掂! Have fun with ...