BOJ 13510 트리와 쿼리 1

ㅎㅇ! 오랜만에 글 쓴다.
사실 그동안 센트로이드 디컴퍼지션 짜다가 짜증나서 얼른 이 문제로 갈아 탔다.
각설하고, 트리와 쿼리 1을 푸는 방법을 알아보자.

선행지식: Heavy Light Decomposition
문제: BOJ13510

우선, HLD 문제 잘 푸는 사람이 이 글을 볼 리는 없다는 가정하에 글을 쓴다.

HLD를 배웠다면 HLD의 작동 메커니즘이 간선이 아닌, 노드 위주의 쿼리를 처리하는 것임을 알 수 있다.
그래서 그냥 HLD를 구현하는 것으론 부족하니 간선의 특성을 노드의 특성으로 바꿔주면 좋겠다는 생각을 했을 수도 있다.

간선의 가중치를 노드의 특성으로

간단하다. 노드에 연결된 어떠한 간선의 가중치를 노드의 각종 정보를 초기화할 때 같이 기록하면 되겠다. 여기서 어떠한은 무엇을 말할까? 이왕이면 각 노드들에 하나만 있으면 좋겠으니 노드에서 부모로 향하는 간선으로 하자.
(한 가지 주의할 점은, 루트 노드의 부모로의 간선의 가중치는 간선의 가중치가 될 수 없는 작은 값이어야 한다는 것이다.)

쿼리의 처리

트리에서 두 노드 간의 경로는 u->lca(u,v)->v임은 돌골렘도 아는 사실이다.
이 문제에서 답을 구할 때는 a->b의 경로와 b->a의 경로가 그 값이 같고, 분할된 두 경로를 통해 전체의 답을 쉽게 구할 수 있다.
따라서 경로 u->lca(u,v)->vu->lca(u,v)v->lca(u,v) 의 경로로 분할하고 각각에 대한 해답을 통해 쉽게 구할 수 있다.

정답 코드

복붙은 돌골렘이 되는 지름길이다. HLD의 구현량이 많은 편이지만, 끝까지 인내하고 한 번쯤은 직접 구현해보길 바란다.

#include<bits/stdc++.h>
using namespace std;
using pii=pair<int,int>;
using ppi=pair<int,pii>;

const int nmax = 100010;

#define fi first
#define se second

#define fastio do{ios::sync_with_stdio(0);cin.tie(0);}while(0)

#define radj(x) for(auto [nxt,wei]:adj[x])

int n,m;
vector<pii> adj[nmax];
vector<ppi> edge;
int seg[nmax<<2];
int w[nmax];
int d[nmax];
int v[nmax];
int sps[20][nmax];
int idx[nmax];
int rev[nmax];
int hld[nmax];

/*Maximum Segment Tree - begin*/
//One-Node Update
void su(int idx,int l,int r,int p,int x){
    if(l>p||r<p)return;
    if(l!=r){
        int mid=l+r>>1;
        su(idx<<1,l,mid,p,x);su((idx<<1)+1,mid+1,r,p,x);
        seg[idx]=max(seg[idx<<1],seg[(idx<<1)+1]);
        return;
    }
    seg[idx]=v[l]=x;
}
//Range Maximum Query
int sq(int idx,int l,int r,int s,int e){
    if(l>e||r<s)return 0;
    if(s<=l&&r<=e)return seg[idx];
    int mid=l+r>>1;
    return max(sq(idx<<1,l,mid,s,e),sq((idx<<1)+1,mid+1,r,s,e));
}
/*Maximum Segment Tree - end*/

//Lowest Common Ancestor
int lca(int u,int v){
    if(d[u]>d[v])swap(u,v);
    for(int i=19;i>=0;i--)if(d[v]-d[u]>=(1<<i))v=sps[i][v];
    if(u==v)return u;
    for(int i=19;i>=0;i--)if(sps[i][u]!=sps[i][v]){
        u=sps[i][u];v=sps[i][v];
    }
    return sps[0][u];
}

/*HLD - begin*/
//Init Weight, Depth, SparseTable
void dfs(int now){
    w[now]=1;
    radj(now)if(!w[nxt]){
        v[nxt]=wei;
        sps[0][nxt]=now;
        d[nxt]=d[now]+1;
        dfs(nxt);
        w[now]+=w[nxt];
    }
}
//DFS ordering
int dn;
void dfs2(int now){
    int cc=-1;
    idx[now]=++dn;
    rev[idx[now]]=now;
    radj(now)
        if(sps[0][nxt]==now&&(cc==-1||w[nxt]>w[cc]))
            cc=nxt;
    if(cc!=-1){
        hld[cc]=hld[now];
        dfs2(cc);
    }
    radj(now)if(sps[0][nxt]==now&&nxt!=cc){
        hld[nxt]=nxt;
        dfs2(nxt);
    }
}
//Update EdgeWeight
void hld_upd(int eidx,int x){
    if(d[edge[eidx].se.fi]>d[edge[eidx].se.se]){
        su(1,1,n,idx[edge[eidx].se.fi],x);
    }
    else{
        su(1,1,n,idx[edge[eidx].se.se],x);
    }
}
//Query_Divided / v is anc of u
int hld_hq(int u,int v){
    int r=0;
    while(hld[u]!=hld[v]){
        r=max(r,sq(1,1,n,idx[hld[u]],idx[u]));
        u=sps[0][hld[u]];
    }
    if(u!=v){
        r=max(r,sq(1,1,n,idx[v]+1,idx[u]));
    }
    return r;
}
//Query
int hld_query(int u,int v){
    int l=lca(u,v);
    return max(hld_hq(u,l),hld_hq(v,l));
}
/*HLD - end*/

//Main
int main(void){
    fastio;
    
    cin>>n;
    edge.resize(n-1);
    for(int i=1;i<n;i++){
        int a,b,c;
        cin>>a>>b>>c;
        edge[i-1]=ppi(c,pii(a,b));
        adj[a].emplace_back(b,c);
        adj[b].emplace_back(a,c);
    }
    
    dfs(1);dfs2(1);
    for(int i=1;i<20;i++){
        for(int j=1;j<=n;j++){
            sps[i][j]=sps[i-1][sps[i-1][j]];
        }
    }
    for(int i=0;i<n-1;i++){
        hld_upd(i,edge[i].fi);
    }
    
    cin>>m;
    while(m--){
        int x,a,b;
        cin>>x>>a>>b;
        if(x==1){
            edge[a-1].fi=b;
            hld_upd(a-1,b);
        }
        else{
            cout<<hld_query(a,b)<<"\n";
        }
    }
}