atc abc460F 思路分享(树的直径,线段树,点分树)

atc abc460F

题意

给定一棵树,初始全黑,\(q\) 个查询,每次将 \(x\) 节点颜色反转,求每次操作后任意两黑点距离的最大值.

\(3\le n \le 10^5\)\(1\le q \le 10^5\).

线段树维护直径做法

思路

相当于维护黑点构成的动态集合的直径,直径是可 merge 的,可以用线段树维护.

具体来说,假设两集合直径端点分别是 \((a_1,b_1)\)\((a_2,b_2)\),合并后集合直径端点 \((a',b')\) 必有 \(a',b' \in \{a_1,b_1,a_2,b_2\}\),暴力枚举二元组即可.

计算任意两点距离可以通过预处理 \(depth\)\(dis(a,b) = depth_a+depth_b-2\cdot depth_{LCA(a,b)}\).

时间复杂度 \(\mathcal{O}(n\log^2 n)\),可以使用 \(\mathcal{O}(1)\) 计算 \(LCA\) 的技术优化到 \(\mathcal{O}(n\log n)\).

代码

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int INF = 1e9;

int n;
vector<vector<int>> adj,nxt;
vector<int> depth;

void dfs(int u,int par){
    for (auto& v:adj[u]){
        if (v==par) continue;
        depth[v] = depth[u]+1;
        nxt[0][v] = u;
        dfs(v,u);
    }
}

int lca(int a,int b){
    if (depth[a]<depth[b]) swap(a,b);
    int diff = depth[a]-depth[b];

    for (int k=20;k>=0;k--){
        if (diff>>k&1){
            a = nxt[k][a];
        }
    }   

    if (a==b) return a;
    for (int k=20;k>=0;k--){
        if (nxt[k][a]!=-1 && nxt[k][b]!=-1 && nxt[k][a]!=nxt[k][b]){
            a = nxt[k][a];
            b = nxt[k][b];
        }
    } 
    return nxt[0][a];
}

int caldis(int a,int b){
    if (a==-1 || b==-1) return -INF;
    return depth[a]+depth[b]-2*depth[lca(a,b)];
}

struct node{
    int a,b;
    node(int a=-1,int b=-1):a(a),b(b){};

    node operator+(const node& o)const {
        vector<int> temp{a,b,o.a,o.b};
        node res;
        int mx = -INF;

        for (int i=0;i<4;i++){
            for (int j=i+1;j<4;j++){
                int d = caldis(temp[i],temp[j]);
                if (d>mx){
                    mx = d;
                    res.a = temp[i];
                    res.b = temp[j];
                }
            }
        }
        return res;
    }
};

struct segmentTree{ 
    int n;
    vector<node> seg;

    segmentTree(int _n){
        n = _n;
        seg.assign(4*n+1,{});
    }

    void build(int rt,int l,int r,vector<node>& a){
        if (l==r){
            seg[rt] = a[l];
            return;
        }   

        int mid = l+r >> 1;
        build(rt<<1,l,mid,a);
        build(rt<<1|1,mid+1,r,a);

        seg[rt] = seg[rt<<1]+seg[rt<<1|1];           
    }

    void build(vector<node>& a){
        build(1,1,n,a);
    } 

    void update(int rt,int l,int r,int pos,node val){
        if (l==r){
            seg[rt] = seg[rt]+val;
            return;
        }       

        int mid = l+r >> 1;
        if (pos<=mid){
            update(rt<<1,l,mid,pos,val);
        }
        else{
            update(rt<<1|1,mid+1,r,pos,val);
        }

        seg[rt] = seg[rt<<1]+seg[rt<<1|1];
    }

    void update(int pos,node val){
        if (pos<1 || pos>n) return;
        update(1,1,n,pos,val);
    }

    void assign(int rt,int l,int r,int pos,node val){
        if (l==r){
            seg[rt] = val;
            return;
        }       

        int mid = l+r >> 1;
        if (pos<=mid){
            assign(rt<<1,l,mid,pos,val);
        }
        else{
            assign(rt<<1|1,mid+1,r,pos,val);
        }

        seg[rt] = seg[rt<<1]+seg[rt<<1|1];
    }

    void assign(int pos,node val){
        if (pos<1 || pos>n) return;
        assign(1,1,n,pos,val);
    }

    node query(int rt,int l,int r,int pos){
        if (l==r){
            return seg[rt];
        }       

        int mid = l+r >> 1;
        if (pos<=mid){
            return query(rt<<1,l,mid,pos);
        }
        else{
            return query(rt<<1|1,mid+1,r,pos);
        }
    }

    node query(int pos){
        if (pos<1 || pos>n) return {};
        return query(1,1,n,pos);
    }

    node query_range(int rt,int l,int r,int x,int y){
        if (r<x || l>y){
            return {};
        }

        if (x<=l && y>=r){
            return seg[rt];
        }

        int mid = l+r >> 1;
        return query_range(rt<<1,l,mid,x,y)+query_range(rt<<1|1,mid+1,r,x,y);
    }

    node query_range(int l,int r){
        if (l<1 || l>n || r<1 || r>n || l>r) return {};
        return query_range(1,1,n,l,r);
    }   
};

void solve(){
    cin >> n;
    adj.resize(n+1);
    for (int i=0;i<n-1;i++){
        int u,v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    depth.assign(n+1,0);
    nxt.assign(21,vector<int>(n+1,-1));    
    dfs(1,-1);

    for (int k=1;k<=20;k++){
        for (int i=1;i<=n;i++){
            if (nxt[k-1][i]==-1) continue;
            nxt[k][i] = nxt[k-1][nxt[k-1][i]];
        }
    }

    segmentTree sg(n);
    vector<int> a(n+1,1);
    {
        vector<node> temp(n+1);
        for (int i=1;i<=n;i++){
            temp[i] = {i,i};
        }
        sg.build(temp);
    }

    int q;
    cin >> q;
    while (q--){
        int x;
        cin >> x;

        if (a[x]==0){
            sg.assign(x,{x,x});
        }
        else{
            sg.assign(x,{-1,-1});
        }   
        a[x]^=1;

        cout << caldis(sg.seg[1].a,sg.seg[1].b) << '\n';
    }   
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int t = 1;
    // cin >> t;
    while (t--) solve();

    return 0;
}

点分树做法

思路

核心思路是对每个重心讨论跨重心的贡献,因为带修的缘故,需要维护所有的信息而不是单单最大值.

对每个重心,维护其与子树中所有黑点的距离,其贡献是最大和次大两距离之和,注意不能来自于同一子树.

这里用到点分树中一个经典 trick,每个节点维护其父节点的信息,记为 \(fa\).

维护所有孩子 \(fa\) 的最大值,记为 \(best\),这样该节点贡献就是 \(best\) 中最大值和次大值之和.

用 multiset 维护所有节点的贡献,记为 \(st\),查询直接输出最大值即可.

\(best\)\(fa\) 实际上都可以用 multiset 维护,但这样实现常数较大,难以在 \(4s\) 内通过,原因在于节点初始全黑,初始对每个节点 update 的开销是巨大的.

事实上,\(fa\) 每次只取最大值,可以用双堆懒删除的技术代替 multiset,使用堆结构的优势在于建堆时间是线性的,这样就解决了初始化成本巨大的问题.

时间复杂度 \(\mathcal{O}(n\log^2 n)\),可以把所有 multiset 都换成堆进一步优化.

代码

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;


void solve(){
    int n;
    cin >> n;

    vector<vector<int>> adj(n+1);
    for (int i=0;i<n-1;i++){
        int u,v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    vector<int> sz(n+1),a(n+1,1);
    vector<bool> vis(n+1,false);
    vector<vector<pair<int,int>>> path(n+1);

    function<void(int,int)> getsz = [&](int u,int par){
        sz[u] = 1;
        for (auto& v:adj[u]){
            if (v==par || vis[v]) continue;
            getsz(v,u);
            sz[u] += sz[v];
        }
    }; 

    auto getsent = [&](int u){
        getsz(u,-1);
        int par = -1;
        int half = sz[u] >> 1;

        while (1){
            bool ok = false;
            for (auto& v:adj[u]){
                if (v==par || vis[v]) continue;
                if (sz[v]>half){
                    par = u;
                    u = v;
                    ok = true;
                    break;
                }
            }
            if (!ok) break;
        }
        return u;
    };

    function<void(int,int,int,int)> dfs = [&](int u,int par,int sent,int dis){
        path[u].emplace_back(dis,sent);
        for (auto& v:adj[u]){
            if (v==par || vis[v]) continue;
            dfs(v,u,sent,dis+1);
        }
    };

    function<void(int)> work = [&](int u){
        vis[u] = true;
        path[u].emplace_back(0,u);

        for (auto& v:adj[u]){
            if (vis[v]) continue;
            dfs(v,u,u,1);
        }

        for (auto& v:adj[u]){
            if (vis[v]) continue;
            work(getsent(v));
        }
    };
    work(getsent(1));

    for (int i=1;i<=n;i++){
        reverse(path[i].begin(),path[i].end());
    }

    vector<vector<int>> fa(n+1),sub(n+1);
    vector<multiset<int>> best(n+1);
    multiset<int> st;

    auto cal = [&](int x){
        if (best[x].size()<2) return 0;
        return *best[x].rbegin()+(*prev(prev(best[x].end())));
    };

    auto pop = [&](int x){
        while (!fa[x].empty() && !sub[x].empty()){
            int a = fa[x].front();
            int b = sub[x].front();
            if (a==b){
                pop_heap(fa[x].begin(),fa[x].end());
                pop_heap(sub[x].begin(),sub[x].end());
                fa[x].pop_back();
                sub[x].pop_back();
            }
            else break;
        }
    };

    auto update = [&](int x,int op){
        int len = path[x].size();
        st.extract(cal(x));
        if (op==0){
            best[x].insert(0);
        }
        else{
            best[x].extract(0);
        }
        st.insert(cal(x));

        for (int i=0;i<len-1;i++){
            st.extract(cal(path[x][i+1].second));
            pop(path[x][i].second);

            if (!fa[path[x][i].second].empty()){
                best[path[x][i+1].second].extract(fa[path[x][i].second].front());
            }

            if (op==0){
                fa[path[x][i].second].push_back(path[x][i+1].first);
                push_heap(fa[path[x][i].second].begin(),fa[path[x][i].second].end());
            }
            else{
                sub[path[x][i].second].push_back(path[x][i+1].first);
                push_heap(sub[path[x][i].second].begin(),sub[path[x][i].second].end());
            }

            pop(path[x][i].second);
            if (!fa[path[x][i].second].empty()){
                best[path[x][i+1].second].insert(fa[path[x][i].second].front());
            }
            st.insert(cal(path[x][i+1].second));
        }
    };

    for (int x=1;x<=n;x++){
        int len = path[x].size();
        best[x].insert(0);

        for (int i=0;i<len-1;i++){
            fa[path[x][i].second].push_back(path[x][i+1].first);
        }
    }    

    for (int x=1;x<=n;x++){
        make_heap(fa[x].begin(),fa[x].end());
        int len = path[x].size();
        if (len>=2 && !fa[x].empty()){
            best[path[x][1].second].insert(fa[x].front());
        }   
    }

    for (int x=1;x<=n;x++){
        st.insert(cal(x));
    }

    int q;
    cin >> q;
    while (q--){
        int x;
        cin >> x;

        update(x,a[x]);
        a[x]^=1;

        cout << *st.rbegin() << '\n';
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int t = 1;
    // cin >> t;
    while (t--) solve();

    return 0;
}
posted @ 2026-08-15 15:00  kzssCCC  阅读(4)  评论(0)    收藏  举报