Gym 100512D Dynamic LCA ((LCA 在线算法DFS+ST) + 分类讨论!!)

6 篇文章 0 订阅

大体题意:

多组数据,每组数据给你一个树(数的节点最多10W),然后有m(m <= 20W)个操作,每一组操作有 换树的树根,和查询u,v节点的最深公共最先?

思路:

这个题目给了自己很大的教训:模板都是次要的,关键还是怎么发挥模板,关键还是思维!,还需要勤加练习!!

比赛过程中,只用了在线求LCA的模板,但思维太局限了,超时了!请教了学长,感觉比较巧妙!

初始化树根只能初始化一次,剩余的换根操作,只能分类讨论进行比较:

刚开始按题目要求来即可,把树根建立为1.

然后用一个变量root记录根的序号!

对于每一个查询,如果root还是1,直接查即可!

如果不是1,就要讨论了:

先看root 和当前 u,v祖先有没有交集,这里说的交集是lca(root,lca(u,v)) != lca表示root 与u,v祖先没有交集,那么换根后,uv祖先不变!

如果有交集就要对每一种情况讨论:

先说下讨论的依据:

①u,v的祖先 是u或者v的某一个!

②u,v的祖先既不是u也不是v!

讨论第一种情况时:

可以看作u,v是一条链,然后讨论root插在哪里,值是多少即可!  大体看图示:


其实这三个位置的root 返回值是一样的,都是 lca(u,root)

然后在交换以下u,v在讨论一遍即可!

对于第二种情况来说:


同理于情况①,讨论仔细即可!

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 200000 + 10;
int rmq[MAXN<<1];
int d[MAXN];
struct ST{
    int mm[MAXN << 1];
    int dp[MAXN << 1][20];
    void init(int n){
        mm[0] = -1;
        for (int i = 1; i <= n; ++i){
            mm[i] = ((i&(i-1)) == 0 ) ? mm[i-1]+1:mm[i-1];
            dp[i][0] = i;
        }
        for (int j = 1; j <= mm[n]; ++j)
            for (int i = 1; i + (1<<j)-1 <= n; ++i)
                dp[i][j] = rmq[dp[i][j-1]] < rmq[dp[i+(1<<(j-1))][j-1] ] ? dp[i][j-1] : dp[i+(1<<(j-1))][j-1];

    }
    int query(int a,int b){
        if (a>b) swap(a,b);
        int K = mm[b-a+1];
        return rmq[dp[a][K]] <= rmq[dp[b-(1<<K)+1][K]] ? dp[a][K] : dp[b-(1<<K)+1 ][K];
    }

};
struct Edge{
    int to, next;
};
Edge edge[MAXN+2];
int tot,head[MAXN];
int F[MAXN << 1];
int P[MAXN];
int cnt;
ST st;
void init(){
    tot = 0;
    memset(head,-1,sizeof head);

}
void addedge(int u,int v){
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
void dfs(int u,int pre,int dep){
    d[u] = dep;
    F[++cnt] = u;
    rmq[cnt] = dep;
    P[u] = cnt;
    for (int i = head[u]; i != -1; i = edge[i].next){
        int v = edge[i].to;
        if (v == pre)continue;
        dfs(v,u,dep+1);
        F[++cnt] = u;
        rmq[cnt] = dep;
    }
}
void LCA_init(int root,int node_num){
    cnt = 0;
    dfs(root,root,0);
    st.init(2*node_num-1);
}
int query_lca(int u,int v){
    return F[st.query(P[u],P[v])];
}

int n;
int solve(int root,int u,int v){
    int lca = query_lca(u,v);
    int lcau = query_lca(u,root);
    int lcav = query_lca(v,root);
    int lca2 = query_lca(root,lca);
    if (root == 1 || lca2 != lca)return lca;
    if (lca == v && lcav == v) return lcau;
    else if (lca == u && lcau == u) return lcav;
    if (lcav == lca) return lcau;
    if (lcau == lca) return lcav;

}
int main(){
    freopen("dynamic.in","r",stdin);
    freopen("dynamic.out","w",stdout);
    while(scanf("%d",&n) == 1 && n){
        init();
        for (int i = 0; i < n-1; ++i){
            int u,v;
            scanf("%d %d",&u, &v);
            addedge(u,v);
            addedge(v,u);
        }
        int m;
        scanf("%d",&m);
        char cmd[3];
        int root = 1;
        LCA_init(root,n);
        for (int i = 0; i < m; ++i){
            scanf("%s",cmd);
            if (cmd[0] == '!'){
                scanf("%d",&root);
            }
            else {
                int u,v;
                scanf("%d %d",&u, &v);
                printf("%d\n",solve(root,u,v));
            }
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值