JZOJ 4289.Mancity

Mancity

Description

Input

Output

SampleInput

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

SampleOutput

1
0
2
2
3
4

Hint

DataConstraint

大致理一会题意,发现就是两个点 u,v 每步向上跳 d 距离,每步不能落在边上,在他们的 lca 处转身,经过 lca 直到相遇,求最少步数
而有了这句话,我们就可以直接模拟 u,v 同时一步一步往上跳,跳到离 lca 最近的点(不超过 lca),再看这两个点的距离,计算贡献

然而太慢,我们发现一步一步向上跳太 naive
又想到倍增就是改一步一步向上跳为一次向上跳 2i步,预处理向上跳 2i 步是谁
那么我们似乎可以同理维护一步跳距离为 d,每步不能落在边上,一次跳 2i 步是谁
记为 top[u][i],表示 u 向上跳 2i步落在哪个点,只用算 top[u][0] 就可以得到其它的 top了。
这样忒慢的一步一步跳变成了 O(logn) 级别的

那么怎么算 top[u][0]
我们如果从 u 处向上一条边一条边地算,那么时间是不可接受的
注意到,utop[u][0] 必然是他的祖先,并且 top[u][0] 必然是 top[fa[u]][0] 或往下
那么我们就可以从 top[fa[u][0]] 处扫,不合法就往下
你向上跳的路径是固定的(你只有一个father),可往下就不一定了
所以我们开一个栈 stackdfs 到当前点 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

倍增,nlogn80

#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 的呀???
咦,题目似乎可以离线!
嗯,那就上 Tarjanlca 吧!
Tarjanlca 大致思想是将询问挂在节点上,遍历到它的时候就去处理询问
按照 dfs 遍历的顺序,我们在遍历完一个节点将它合并的它的祖先节点
如果当前点为 u ,它的询问对象 v 已经被访问过,那么他们的最近公共祖先就是 find(v)
当然,不明白的话就去查查百度

注意,如果 u,v 是祖孙关系,那么处理询问时它们两个都会算
所以存储时要开两倍数组

不过,上面的 Tarjanlca 快很多了,可计算答案怎么搞?
重新记 top[u] 表示原先的 top[u][0]
其实,我们注意到 u 所对应的 top[u] 是唯一的,它们不会形成环
于是所有的 top 关系又组成了一棵树
那么求 u,v 最接近公共祖先的两个节点时也可以同 Tarjan 一样使用并查集
我们在遍历完一个节点将它合并的 top 节点
将询问挂在 lca
因为正在处理 lca ,所以查询 u,v 两点各自的 top 树的祖先绝对不会超过 lcalca 还没合并到它的 top
所以我们直接 find(u),find(v),就得到了离 lca 最近的点。当然,求步数时给并查集加个权值就好了
然后转弯时上文同理
详见 Code2

Code2

Tarjan , 并查集, O(nα(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]);
}
posted @   leiyuanze  阅读(161)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示