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\)
然而太慢,我們發現一步一步向上跳太 \(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] = 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]);
}