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, ...
随机推荐
- Azure DevOps Server 用户组加入 Azure AD Domain Service 管理用户
一,引言 今天我们继续讲解 Azure DevOps Server 的内容,对于管理用户组除了在 Azure DevOps Server 服务器上添加管理员方式外,还有没有其他方式,Azure Dev ...
- KVC原理与数据筛选
作者:宋宏帅 1 前言 在技术论坛中看到一则很有意思的KVC案例: @interface Person : NSObject @property (nonatomic, copy) NSString ...
- 100以内能被7整除的前五个数-Java
import java.util.HashSet; import java.util.Set; public class Demo { //100以内能够被7整除的前五个数 public static ...
- Datawhale组队学习_Task03:详读西瓜书+南瓜书第4章
第4章 决策树 4.1 基本流程 #输入:训练集D={${(x_1,y_1),(x_2,y_2),...,(x_m,y_m)}$}; #属性集A=${{a_1,a_2,...,a_d}}$. #过程: ...
- 老板:你为什么要选择 Vue?
大家好,我是 Kagol,Vue DevUI 开源组件库和 EditorX 富文本编辑器创建者,专注于前端组件库建设和开源社区运营. 假如你是团队的前端负责人,现在老板要拓展新业务,需要开发一个 We ...
- 【SQL基础】基础查询:所有列、指定列、去重、限制行数、改名
〇.建表数据 drop table if exists user_profile; CREATE TABLE `user_profile` ( `id` int NOT NULL, `device_i ...
- appium环境搭建(从入门到放弃)
一.appium环境搭建 1.python3 python3的下载安装这里就不多做介绍了,当然你也可以选择自己喜欢的语音,比如java.... 2.jdk 1)下载地址 官网(需登录账号): http ...
- SQL语句查询关键字 多表查询
目录 SQL语句查询关键字 select from 编写顺序和查询数据 前期数据准备 编写SQL语句的小技巧 查询关键字之筛选 where 逻辑运算符 not and or between not b ...
- Win10下SDK Manager应用程序闪退问题的解决方法
SDK Manager闪退原因:未找到Java的正确路径 解决办法: 1.在压缩包中找到Android.bat文件,右键编辑 2.打开的Android文件内容,找到如图的几行代码 将上面的代码替换成: ...
- CH334H与GL85x功能对比(过流检测与电源控制说明)
CH334H与GL85x功能对比 CH334H是符合 USB2.0 协议规范的高性能MTT 4 端口 USB2.0 HUB 控制器芯片,高ESD特性,工业级设计,外围精简,可应用于计算机和工控机主板 ...