1. 程式人生 > >How far away ? HDU - 2586(LCA Tarjan離線方法 or 倍增線上方法)

How far away ? HDU - 2586(LCA Tarjan離線方法 or 倍增線上方法)

How far away ? HDU - 2586

題目連結

題意:給出一棵樹,問任意兩點的間的最小距離;
思路:考慮本題,若t時a, b的LCA,r是樹根那麼dis[a, b]=dis[r, a]+dis[r, b]-2*dis[r, t];
dis[r, p] (根到p點的距離) 可以通過dfs求得,那麼找出[a, b]的LCA就萬事大吉了;
首先是Tarjan離線方法:
1. 任選一個根節點;
2. 遍歷當前節點u的所有子節點v,並標記;
3. 若當前點有子節點返回2, 否則進入下一步;
4. 用並查集合並將 v合併到u
5. 遍歷所有詢問中與當前點u相關的點v,如果x被標記(被訪問過),那麼lca(u, v)=find(v);
複雜度是O(n+q);

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=4e4+10;
struct node{
    int v, nxt, w;
}edge[maxn<<1];
int head[maxn], cnt;
void add(int u, int v, int w){
    edge[cnt]=node{v, head[u], w};
    head[u]=cnt++;
}   
int n, m;
int per[maxn], vis[maxn], dis[maxn];
vector<int
> q[maxn]; map<pair<int, int>, int> mp; void init(){ cnt=0; mp.clear(); for(int i=0; i<=n; i++){ per[i]=i; head[i]=-1; vis[i]=0; q[i].clear(); } } int find(int x){ return per[x]==x?per[x]:per[x]=find(per[x]); } void Union(int u, int v){ int
fu=find(u), fv=find(v); per[fv]=fu; } struct query{ int u, v; }p[maxn]; void tarjan(int u, int fa){ for(int i=head[u]; i!=-1; i=edge[i].nxt){ int v=edge[i].v, w=edge[i].w; if(v==fa) continue; dis[v]=dis[u]+w; tarjan(v, u); Union(u, v); vis[v]=1; } for(int i=0; i<q[u].size(); i++){ int v=q[u][i]; if(vis[v]){ int t=find(v); int d=dis[u]+dis[v]-2*dis[t]; mp.insert(make_pair(make_pair(u, v), d)); } } } int main(){ int T; scanf("%d", &T); while(T--){ scanf("%d%d", &n, &m); init(); for(int i=1; i<n; i++){ int u, v, w; scanf("%d%d%d", &u, &v, &w); add(u, v, w); add(v, u, w); } for(int i=0; i<m; i++){ int u, v; scanf("%d%d", &u, &v); p[i]=query{u, v}; q[u].push_back(v); q[v].push_back(u); } dis[1]=0; tarjan(1, 1); for(int i=0; i<m; i++){ if(mp.count(make_pair(p[i].u, p[i].v))){ printf("%d\n", mp[make_pair(p[i].u, p[i].v)]); } else if(mp.count(make_pair(p[i].v, p[i].u))){ printf("%d\n", mp[make_pair(p[i].v, p[i].u)]); } } } return 0; }

接著是倍增線上法:
解題步驟:
1. 將u, v調到同一深度;
2. 一起向上跳直到u=v;
我們當然可以一步步的向上跳,但是可想而知複雜度是O(n)的,然後q個詢問,整體就是O(nq)的複雜度;顯然不合適;那麼久儘可能的跳的多一點;
令jump[i][j] 表示在i點向上跳2^j步,那麼jump[i][j] = jump[jump[i][j-i]][j-1];

#include <bits/stdc++.h>
using namespace std;
const int maxn=4e4+10;
struct node{
    int v, nxt, w;
}edge[maxn<<1];
int head[maxn], cnt;
void add(int u, int v, int w){
    edge[cnt]=node{v, head[u], w};
    head[u]=cnt++;
}
int n, m, deep[maxn], jump[maxn][15], dis[maxn], k;
void dfs(int u, int fa, int d){
    deep[u]=d;
    for(int i=head[u]; i!=-1; i=edge[i].nxt){
        int v=edge[i].v, w=edge[i].w;
        if(v==fa) continue;
        dis[v]=dis[u]+w;
        jump[v][0]=u;
        for(int i=1; (1<<i)<=n; i++){
            jump[v][i]=jump[jump[v][i-1]][i-1];
            if(k<i) k=i;
        }
        dfs(v, u, d+1);
    }
}
void init(){
    memset(jump, 0, sizeof(jump));
    memset(deep, 0, sizeof(deep));
    dis[1]=0;
    dfs(1, 1, 0);
}
int LCA(int u, int v){
    if(deep[u]<deep[v]) swap(u, v);
    for(int j=k; j>=0; j--){
        if(deep[u]-(1<<j)>=deep[v]){
            u=jump[u][j];
        }
    }
    if(u==v) return u;
    for(int j=k; j>=0; j--){
        if(jump[u][j]!=jump[v][j]){
            u=jump[u][j];
            v=jump[v][j];
        }
    }
    return jump[u][0];
}
int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &m);
        memset(head, -1, sizeof(head));
        cnt=0;
        for(int i=1; i<n; i++){
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            add(u, v, w);
            add(v, u, w);
        }
        init();
        while(m--){
            int u, v;
            scanf("%d%d", &u, &v);
            int t=LCA(u, v);
            printf("%d\n", dis[u]+dis[v]-2*dis[t]);
        }
    }
    return 0;
}