JZOJ 4289.Mancity
\(Mancity\)
\(Description\)

\(Input\)

\(Output\)

\(Sample Input\)
8 3 6
1 2
1 1
3 2
4 2
5 1
6 1
6 2
4 1
3 3
2 4
4 2
2 5
8 2
\(Sample Output\)
1
0
2
2
3
4
\(Hint\)

\(Data Constraint\)

大致理一会题意,发现就是两个点 \(u,v\) 每步向上跳 \(d\) 距离,每步不能落在边上,在他们的 \(lca\) 处转身,经过 \(lca\) 直到相遇,求最少步数
而有了这句话,我们就可以直接模拟 \(u,v\) 同时一步一步往上跳,跳到离 \(lca\) 最近的点(不超过 \(lca\)),再看这两个点的距离,计算贡献
然而太慢,我们发现一步一步向上跳太 \(naive\)
又想到倍增就是改一步一步向上跳为一次向上跳 \(2^i\)步,预处理向上跳 \(2^i\) 步是谁
那么我们似乎可以同理维护一步跳距离为 \(d\),每步不能落在边上,一次跳 \(2^i\) 步是谁
记为 \(top[u][i]\),表示 \(u\) 向上跳 \(2^i\)步落在哪个点,只用算 \(top[u][0]\) 就可以得到其它的 \(top\)了。
这样忒慢的一步一步跳变成了 \(O(logn)\) 级别的
那么怎么算 \(top[u][0]\)?
我们如果从 \(u\) 处向上一条边一条边地算,那么时间是不可接受的
注意到,\(u\) 的 \(top[u][0]\) 必然是他的祖先,并且 \(top[u][0]\) 必然是 \(top[fa[u]][0]\) 或往下
那么我们就可以从 \(top[fa[u][0]]\) 处扫,不合法就往下
你向上跳的路径是固定的(你只有一个father),可往下就不一定了
所以我们开一个栈 \(stack\),\(dfs\) 到当前点 \(u\),将它入栈,那么你求 \(top[fa[u]][0]\) 往下跳时,必然是 \(top[fa[u]][0]\) 在栈中的编号加一(根据 \(dfs\) 遍历的顺序可得)
为了方便处理,我们让 \(top[u][0]\) 先表示 \(top[u][0]\) 在栈中的编号
然后回溯时让 \(top[u][0] = stack[top[u][0]]\),即变为具体的点
详细细节见 \(Code1\)
\(Code1\)
倍增,\(nlogn\),\(80\) 分
#include<cstdio>
#include<iostream>
using namespace std;
const int N = 5e5 + 5;
int n , d , q , fa[N] , anc[N][25] , f[N][25];
int tot , dep[N] , h[N] , dis[N] , st[N] , top;
struct edge{
int nxt , to;
}e[2 * N];
inline void add(int x , int y)
{
e[++tot].to = y;
e[tot].nxt = h[x];
h[x] = tot;
}
inline void dfs(int x)
{
st[++top] = x;
anc[x][0] = max(1 , anc[fa[x]][0]); //最近跳到根1,anc即上文提及的top,先存栈中的编号
while (dis[x] - dis[st[anc[x][0]]] > d) anc[x][0]++; //跳不了那么远,换栈中下一个
for(register int i = 1; i <= 21; i++)
if (f[x][i - 1]) f[x][i] = f[f[x][i - 1]][i - 1];
else break;
for(register int i = h[x]; i; i = e[i].nxt)
{
int v = e[i].to;
dep[v] = dep[x] + 1 , f[v][0] = x;
dfs(v);
}
anc[x][0] = st[anc[x][0]]; //回溯变具体的点
--top;
}
inline int LCA(int x , int y)
{
if (dep[x] < dep[y]) swap(x , y);
int deep = dep[x] - dep[y];
for(register int i = 0; i <= 21; i++)
if (deep & (1 << i)) x = f[x][i];
if (x == y) return x;
for(register int i = 21; i >= 0; i--)
if (f[x][i] != f[y][i]) x = f[x][i] , y = f[y][i];
return f[x][0];
}
inline int getans(int x , int y)
{
int lca = LCA(x , y) , res = 0;
//根据求出来的anc,让u,v同时往上跳
for(register int i = 21; i >= 0; i--)
if (dis[anc[x][i]] > dis[lca]) x = anc[x][i] , res = res + (1 << i);
for(register int i = 21; i >= 0; i--)
if (dis[anc[y][i]] > dis[lca]) y = anc[y][i] , res = res + (1 << i);
if (x == y) return res;
//处理转弯处需要的步数
if (dis[x] + dis[y] - 2 * dis[lca] <= d) return res + 1;
else return res + 2;
}
int main()
{
freopen("mancity.in" , "r" , stdin);
freopen("mancity.out" , "w" , stdout);
scanf("%d%d%d" , &n , &d , &q);
for(register int i = 2; i <= n; i++)
{
scanf("%d%d" , &fa[i] , &dis[i]);
dis[i] += dis[fa[i]];
add(fa[i] , i);
}
dfs(1);
for(register int i = 1; i <= n; i++)
for(register int j = 1; j <= 21; j++)
if (anc[i][j - 1]) anc[i][j] = anc[anc[i][j - 1]][j - 1];
else break;
int x , y;
for(register int i = 1; i <= q; i++)
{
scanf("%d%d" , &x , &y);
printf("%d\n" , getans(x , y));
}
}
正如你所见 \(O(nlogn)\) 竟然过不了
出题人毒瘤卡 \(O(nlogn)\) !
那我还要更快?!
可求 \(lca\),树剖和倍增都是 \(nlogn\) 的呀???
咦,题目似乎可以离线!
嗯,那就上 \(Tarjan\) 求 \(lca\) 吧!
\(Tarjan\) 求 \(lca\) 大致思想是将询问挂在节点上,遍历到它的时候就去处理询问
按照 \(dfs\) 遍历的顺序,我们在遍历完一个节点后将它合并的它的祖先节点
如果当前点为 \(u\) ,它的询问对象 \(v\) 已经被访问过,那么他们的最近公共祖先就是 \(find(v)\)
当然,不明白的话就去查查百度
注意,如果 \(u,v\) 是祖孙关系,那么处理询问时它们两个都会算
所以存储时要开两倍数组
不过,上面的 \(Tarjan\) 求 \(lca\) 快很多了,可计算答案怎么搞?
重新记 \(top[u]\) 表示原先的 \(top[u][0]\)
其实,我们注意到 \(u\) 所对应的 \(top[u]\) 是唯一的,它们不会形成环
于是所有的 \(top\) 关系又组成了一棵树
那么求 \(u,v\) 最接近公共祖先的两个节点时也可以同 \(Tarjan\) 一样使用并查集
我们在遍历完一个节点后将它合并的 \(top\) 节点
将询问挂在 \(lca\) 处
因为正在处理 \(lca\) ,所以查询 \(u,v\) 两点各自的 \(top\) 树的祖先绝对不会超过 \(lca\)(\(lca\) 还没合并到它的 \(top\))
所以我们直接 \(find(u),find(v)\),就得到了离 \(lca\) 最近的点。当然,求步数时给并查集加个权值就好了
然后转弯时上文同理
详见 \(Code2\)
\(Code2\)
\(Tarjan\) , 并查集, \(O(n \alpha(n))\),\(100\) 分
#include<cstdio>
#include<iostream>
using namespace std;
const int N = 5e5 + 5;
int n , d , q , top[N] , stop , stack[N];
int tot1 , tot2 , tot3 , tot4 , h1[N] , h2[N] , h3[N] , h4[N];
int dis[N] , fa[N] , vis[N] , ask[N][3] , ans[N] , dist[N];
struct edge{
int nxt , to;
}e1[N] , e2[N] , e4[2 * N];
struct edgeask{
int nxt , to , id;
}e3[2 * N];
inline void add1(int x , int y) //原来的树
{
e1[++tot1].to = y;
e1[tot1].nxt = h1[x];
h1[x] = tot1;
}
inline void add2(int x , int y) //Top关系树
{
e2[++tot2].to = y;
e2[tot2].nxt = h2[x];
h2[x] = tot2;
}
inline void add3(int x , int y , int z) //ask,Tarjan时用来求一个点和其询问对象的 $lca$
{
e3[++tot3].to = y;
e3[tot3].id = z;
e3[tot3].nxt = h3[x];
h3[x] = tot3;
}
inline void add4(int x , int y) //lca处处理询问,即在询问挂在lca处,遍历到时再处理
{
e4[++tot4].to = y;
e4[tot4].nxt = h4[x];
h4[x] = tot4;
}
inline int find1(int x) //tarjan时所用的并查集的find
{
if (fa[x] == x) return x;
fa[x] = find1(fa[x]);
return fa[x];
}
inline int find2(int x) //top关系树所用的并查集的find
{
if (top[x] == x) return x;
int t = top[x];
top[x] = find2(top[x]);
dist[x] += dist[t]; //路压合并时,加上它father的权值
return top[x];
}
inline void dfs1(int u)
{
//同理计算top
stack[++stop] = u;
top[u] = max(1 , top[fa[u]]);
while (dis[u] - dis[stack[top[u]]] > d) top[u]++;
for(register int i = h1[u]; i; i = e1[i].nxt)
dfs1(e1[i].to);
top[u] = stack[top[u]]; //更换为具体点
--stop;
add2(top[u] , u);
}
inline void dfs2(int u)
{
top[u] = u , fa[u] = u , vis[u] = 1;
for(register int i = h1[u]; i; i = e1[i].nxt)
{
dfs2(e1[i].to);
fa[e1[i].to] = u;
}
for(register int i = h3[u]; i; i = e3[i].nxt) //求lca
{
int v = e3[i].to;
if (!vis[v]) continue;
add4(find1(v) , e3[i].id);
}
for(register int i = h4[u]; i; i = e4[i].nxt) //lca处处理询问
{
int id = e4[i].to , x = ask[id][0] , y = ask[id][1];
int xx = x , yy = y;
x = find2(x) , y = find2(y); //求两个浅点
int m = dis[x] + dis[y] - dis[u] * 2;
ans[id] = dist[xx] + dist[yy] + (m > 0) + (m > d);
}
for(register int i = h2[u]; i; i = e2[i].nxt) top[e2[i].to] = u , dist[e2[i].to] = 1; //维护top
}
int main()
{
freopen("mancity.in" , "r" , stdin);
freopen("mancity.out" , "w" , stdout);
scanf("%d%d%d" , &n , &d , &q);
for(register int i = 2; i <= n; i++)
{
scanf("%d%d" , &fa[i] , &dis[i]);
dis[i] += dis[fa[i]];
add1(fa[i] , i);
}
dfs1(1);
for(register int i = 1; i <= q; i++)
{
scanf("%d%d" , &ask[i][0] , &ask[i][1]);
add3(ask[i][0] , ask[i][1] , i) , add3(ask[i][1] , ask[i][0] , i); //将询问挂上去
}
dfs2(1);
for(register int i = 1; i <= q; i++) printf("%d\n" , ans[i]);
}
JZOJ 4289.Mancity的更多相关文章
- HDU 4289:Control(最小割)
http://acm.hdu.edu.cn/showproblem.php?pid=4289 题意:有n个城市,m条无向边,小偷要从s点开始逃到d点,在每个城市安放监控的花费是sa[i],问最小花费可 ...
- hdu 4289 最大流拆点
大致题意: 给出一个又n个点,m条边组成的无向图.给出两个点s,t.对于图中的每个点,去掉这个点都需要一定的花费.求至少多少花费才能使得s和t之间不连通. 大致思路: 最基础的拆点最大 ...
- hdu 4289 Control(最小割 + 拆点)
http://acm.hdu.edu.cn/showproblem.php?pid=4289 Control Time Limit: 2000/1000 MS (Java/Others) Mem ...
- (jzoj snow的追寻)线段树维护树的直径
jzoj snow的追寻 DFS序上搞 合并暴力和,记录最长链和当前最远点,距离跑LCA # include <stdio.h> # include <stdlib.h> # ...
- [jzoj]3506.【NOIP2013模拟11.4A组】善良的精灵(fairy)(深度优先生成树)
Link https://jzoj.net/senior/#main/show/3506 Description 从前有一个善良的精灵. 一天,一个年轻人B找到她并请他预言他的未来.这个精灵透过他的水 ...
- [jzoj]3468.【NOIP2013模拟联考7】OSU!(osu)
Link https://jzoj.net/senior/#main/show/3468 Description osu 是一款群众喜闻乐见的休闲软件. 我们可以把osu的规则简化与改编成以下的样子: ...
- [jzoj]5478.【NOIP2017提高组正式赛】列队
Link https://jzoj.net/senior/#main/show/5478 Description Sylvia 是一个热爱学习的女孩子. 前段时间,Sylvia 参加了学校 ...
- [jzoj]1115.【HNOI2008】GT考试
Link https://jzoj.net/senior/#main/show/1115 Description 申准备报名参加GT考试,准考证号为n位数X1X2X3...Xn-1Xn(0<=X ...
- [jzoj]2538.【NOIP2009TG】Hankson 的趣味题
Link https://jzoj.net/senior/#main/show/2538 Description Hanks 博士是BT (Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫H ...
- [jzoj]4216.【NOIP2015模拟9.12】平方和
Link https://jzoj.net/senior/#main/show/4216 Description 给出一个N个整数构成的序列,有M次操作,每次操作有一下三种: ①Insert Y X, ...
随机推荐
- C# Panel动态添加滚动条
/// <summary> /// panel控件的事件:在向该控件添加控件时发生 /// </summary> private void panel1_ControlAdde ...
- 关于 python3 中的多线程的问题及理解
什么是 join() ? join() 有什么用? 答:join() 有一个参数是timeout 设置超时,这里的超时都是针对主线程的,当子线程设置为 join(1) 后,则主线程会等待这个线程 1 ...
- 【大数据-课程】高途-天翼云侯圣文-Day3-实时计算原理解析
〇.老师及课程介绍 一.今日内容 二.实时计算理论解析 1.什么是实时计算 微批处理.流式处理.实时计算 水流和车流的例子 spark streaming就是一种微批处理,水满了才处理,进入下一个地方 ...
- 12V转5V降压芯片,12V转3.3V稳压芯片电路图
12V转5V应用中,大多要求会输出电流高的,稳压LDO就不能满足了,需要使用DC-DC降压芯片来持续稳压5V,输出电流1000MA,2000MA,3000MA,5000MA等.不同的输出电流可以选择适 ...
- Django视图层/FBV与CBV/jsonResponse对象/form表单上传文件/request对象获取文件
目录 虚拟环境 视图层views 三板斧 jsonResponse对象 form如何上传文件/request对象获取文件 request对象方法 FBV与CBV CBV源码解析 虚拟环境 每创建一个虚 ...
- ArcObjects SDK开发 017 在ArcObject SDK 中使用Toolbox
1.Geoprocessor和IGPProcess Geoprocessor是ArcObjects SDK中定义Tool执行器.IGPProcess接口是ArcObjects SDK中定义的ArcTo ...
- week_11
Andrew Ng 机器学习笔记 ---By Orangestar Week_11(the Last Week!!!!) Congratulations on making it to the ele ...
- 6、SQL模糊查询LIKE concat用法
concat用来拼接查询的字符串: SELECT * FROM `page_demo` WHERE name LIKE concat('%',#{name},'%') 模糊查询: 1.%代表零个或多个 ...
- 手写Pinia存储的数据持久化插件
Pinia和Vuex的通病 Pinia和vuex的通病就是,页面刷新会导致数据丢失 解决通病 一.新建store import { defineStore } from 'pinia' //单独存放S ...
- Potree 001 Potree介绍
1.Potree是什么 Potree是一种基于WebGL的点云数据可视化解决方案,包含点云数据转化,以及进行可视化的源码.该解决方案的主要优势在于对点云数据进行了多尺度的管理,在数据传输和可视化上都做 ...