\(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的更多相关文章

  1. HDU 4289:Control(最小割)

    http://acm.hdu.edu.cn/showproblem.php?pid=4289 题意:有n个城市,m条无向边,小偷要从s点开始逃到d点,在每个城市安放监控的花费是sa[i],问最小花费可 ...

  2. hdu 4289 最大流拆点

    大致题意:     给出一个又n个点,m条边组成的无向图.给出两个点s,t.对于图中的每个点,去掉这个点都需要一定的花费.求至少多少花费才能使得s和t之间不连通. 大致思路:     最基础的拆点最大 ...

  3. hdu 4289 Control(最小割 + 拆点)

    http://acm.hdu.edu.cn/showproblem.php?pid=4289 Control Time Limit: 2000/1000 MS (Java/Others)    Mem ...

  4. (jzoj snow的追寻)线段树维护树的直径

    jzoj snow的追寻 DFS序上搞 合并暴力和,记录最长链和当前最远点,距离跑LCA # include <stdio.h> # include <stdlib.h> # ...

  5. [jzoj]3506.【NOIP2013模拟11.4A组】善良的精灵(fairy)(深度优先生成树)

    Link https://jzoj.net/senior/#main/show/3506 Description 从前有一个善良的精灵. 一天,一个年轻人B找到她并请他预言他的未来.这个精灵透过他的水 ...

  6. [jzoj]3468.【NOIP2013模拟联考7】OSU!(osu)

    Link https://jzoj.net/senior/#main/show/3468 Description osu 是一款群众喜闻乐见的休闲软件. 我们可以把osu的规则简化与改编成以下的样子: ...

  7. [jzoj]5478.【NOIP2017提高组正式赛】列队

    Link https://jzoj.net/senior/#main/show/5478 Description Sylvia 是一个热爱学习的女孩子.       前段时间,Sylvia 参加了学校 ...

  8. [jzoj]1115.【HNOI2008】GT考试

    Link https://jzoj.net/senior/#main/show/1115 Description 申准备报名参加GT考试,准考证号为n位数X1X2X3...Xn-1Xn(0<=X ...

  9. [jzoj]2538.【NOIP2009TG】Hankson 的趣味题

    Link https://jzoj.net/senior/#main/show/2538 Description Hanks 博士是BT (Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫H ...

  10. [jzoj]4216.【NOIP2015模拟9.12】平方和

    Link https://jzoj.net/senior/#main/show/4216 Description 给出一个N个整数构成的序列,有M次操作,每次操作有一下三种: ①Insert Y X, ...

随机推荐

  1. 关于python转义字符在正则匹配中的问题研究

    问题 首先看一个问题: import re text = r"\学" print(text) zz = r"\学" result = re.findall(zz ...

  2. 关于linux mint新增加的鼠标样式的示例图片不能正确显示的解决办法

    前言 我相信你在linux mint 做鼠标主题美化的时候一定遇到过这样的问题 没错!!! 下载的鼠标的主题的示例图片不能正确显示,当然这样虽然不影响正常的鼠标主题更换使用,但是对于我这种强迫症来说简 ...

  3. js- day03- 将数据变成柱形图

    柱形图的渲染 * {             margin: 0;             padding: 0;         }  .box {             display: fle ...

  4. Excel2010表格内容被加密,无法编辑内容。

    Sub PasswordBreaker() Dim i As Integer, j As Integer, k As Integer Dim l As Integer, m As Integer, n ...

  5. K8S的架构及工作原理

    1.Master和Node 1).Master K8S中的Master是集群控制节点,负责整个集群的管理和控制 在Master上运行着以下关键进程: kube-apiserver:提供了HTTP Re ...

  6. socket模块/TCP协议/黏包处理

    socket模块 如果我们需要编写基于网络进行数据交互的程序 意味着我们需要自己通过代码来控制我们之前 所学习的OSI七层(很繁琐 很复杂 类似于我们自己编写操作系统) socket类似于操作系统 封 ...

  7. WPF中的“资源”

    WPF中的"资源" 资源概述 WPF中的资源的概念有点类似 web 技术中的静态资源的概念.可以是一个样式,也可以是一个button的边框设置集合. 可以简单的将资源分为如下几个类 ...

  8. week_5

    Andrew Ng机器学习笔记---by Orangestar Week_5 重点:反向传播算法,backpropagation 1. Cost Function神经元的代价函数 回顾定义:(上节回顾 ...

  9. vue项目 h5上拉加载(分页功能)

    <template> <div class="receivable"> <div class="application-header fle ...

  10. 红袖添香,绝代妖娆,Ruby语言基础入门教程之Ruby3基础语法,第一次亲密接触EP01

    书接上回,前一篇我们在全平台构建好了Ruby3的开发环境,现在,可以和Ruby3第一次亲密接触了. Ruby是一门在面向对象层面无所不用其极的解释型编程语言. 我们可以把编写Ruby代码看作是一场行为 ...