首页 > 基础资料 博客日记

ABC460F 题解

2026-06-10 11:00:04基础资料围观1

这篇文章介绍了ABC460F 题解,分享给大家做个参考,收藏极客资料网收获更多编程知识

原题链接

闲话

赛时看到 F 马上就想到点分树,只剩十分多钟口胡了一下就跑了。

赛后看题解发现全是线段树分治做的,去原题 P2056 学习了一下点分树做法。发现赛时的口胡离正解还差得远。

思路

首先做一个重链剖分,进而可以以 \(O(\log n)\) 的时间求出任意两点间的距离。

把点分树建出来,对于节点 \(u\),只要考虑在 \(u\) 子树内、经过 \(u\) 的路径 \((v , w)\)。也就是说,\(v,w\)\(u\) 的两个不同的孩子的子树中,或者 \(v,w\) 有一个是 \(u\)。这些路径的最大长度记为 \(ans_u\),那么全局答案就是 \(\max_{u = 1}^{n} ans_u\)

具体如何求出 \(ans_u\)

下面记点分树上 \(u\) 的父亲为 \(up_u\)

路径是由两段以 \(u\) 为端点的路径拼起来的,所以我们只要找到这些路径长度最大值与次大值。 将 \(u\) 子树内所有点到 \(up_u\) 的距离存在集合 \(dis\_to\_up_u\) 中(这个集合肯定要用数据结构维护啦,具体后面会讲)。然后对于 \(u\) 再维护一个集合 \(max\_in\_every\_son_u\)(这个也要用数据结构维护,具体后面会讲),里面有每个 \(u\) 在点分树上的孩子子树内所有黑点到 \(u\) 距离的最大值。当然,如果 \(u\) 也是黑点,还要在集合中加入它到它自己的距离,也就是 \(0\)。那么 \(ans_u\) 就是 \(max\_in\_every\_son_u\) 中的最大值与次大值之和。我们还要统计全局答案,开一个集合 \(all\) 存所有的 \(ans_u\)

最开始的 \(dis\_to\_up\) 可以暴力直接求出来,因为点分树树高为 \(\log n\),一个点只会加入到它祖先的 \(dis\_to\_up\) 中,而它的祖先只有 \(\log n\) 个。对于所有点 \(u\),将它的 \(dis\_to\_up_u\) 的最大值挑出来,扔到 \(max\_in\_every\_son_{{up}_u}\) 中,这样我们初始化就完成了。

代码长这样。

fo(u , 1 , n){ // for(int u = 1 ; u <= n ; u++) 的意思
    int cur = u;
    while(up[cur]){
        dis_to_up[cur].push(dis(up[cur] , u));
        cur = up[cur];
    }
}
fo(u , 1 , n){
    max_in_every_son[u].push(0); // 别忘了它自己
    if(up[u] != 0)
        max_in_every_son[up[u]].push(dis_to_up[u].query1()); // query1 是查询最大值的函数
}
fo(u , 1 , n){
    all.push(max_in_every_son[u].query12()); // query12 是查询最大值与次大值之和的函数
}

现在我们考虑修改一个点的颜色会改变什么。显然它的所有祖先的 \(dis\_to\_up\)\(max\_in\_every\_son\) 都要修改。具体的,设 \(v\)\(u\) 的某一个祖先,那么将 \(u\) 改为白点 / 黑点就是在 \(dis\_to\_up_v\) 中删除 / 加入 \(dis(u , up_v)\)。然后 \(dis\_to\_up_v\) 的最大值要贡献给 \(max\_in\_every\_son_{up_v}\),那么要在 \(max\_in\_every\_son_{up_v}\) 中删去原来的最大值,再加入 \(dis\_to\_up_v\) 新的最大值。这样 \(ans_{up_v}\) 也改变了,在 \(all\) 中删除旧的 \(ans_{up_v}\),加入新的。这样修改操作也完成啦。

代码长这样。

fo(u , 1 , n) black[u] = 1;
cin >> q;
while(q--){
    int u;
    cin >> u;
    if(black[u] == 1){
        black[u] = 0;
        int pre1 = max_in_every_son[u].query12(); // 别忘了它自己
        max_in_every_son[u].remove(0);
        int now1 = max_in_every_son[u].query12();
        all.remove(pre1) , all.push(now1);
        int cur = u;
        while(up[cur]){
            int pre = dis_to_up[cur].query1();
            dis_to_up[cur].remove(dis(up[cur] , u));
            int now = dis_to_up[cur].query1();
            int pre1 = max_in_every_son[up[cur]].query12();
            max_in_every_son[up[cur]].remove(pre);
            max_in_every_son[up[cur]].push(now);
            int now1 = max_in_every_son[up[cur]].query12();
            all.remove(pre1) , all.push(now1);
            cur = up[cur];
        }
    }
    else{
        black[u] = 1;
        int pre1 = max_in_every_son[u].query12();
        max_in_every_son[u].push(0);
        int now1 = max_in_every_son[u].query12();
        all.remove(pre1) , all.push(now1);
        int cur = u;
        while(up[cur]){
            int pre = dis_to_up[cur].query1();
            dis_to_up[cur].push(dis(up[cur] , u));
            int now = dis_to_up[cur].query1();
            int pre1 = max_in_every_son[up[cur]].query12();
            max_in_every_son[up[cur]].remove(pre);
            max_in_every_son[up[cur]].push(now);
            int now1 = max_in_every_son[up[cur]].query12();
            all.remove(pre1) , all.push(now1);
            cur = up[cur];
        }
    }
    cout << all.query1() << "\n";
}

看着很长,但黑变白,白变黑只有 removepush 的区别,其他代码是重复的。

接着考虑 \(dis\_to\_up_u\)\(max\_in\_every\_son_u\)\(all\) 三个集合如何维护。它们要实现的功能有:

  • 加入一个数

  • 删除一个数

  • 查询最大值

  • 查询最大值与次大值之和

这个可以用两个优先队列实现,集合中的数存在一个队列 \(a\) 中,删除的数暂时存在另一个队列 \(del\) 中。取出最大值时,如果它也是 \(del\) 中的最大值就两个都弹掉。

struct Natho_nA{
    priority_queue<int> a , del;
    void push(int x){
        a.push(x);
    }
    void remove(int x){
        del.push(x);
    }
    int query1(){
        while(a.size() and del.size() and a.top() == del.top()){
            a.pop() , del.pop();
        }
        if(a.size()) return a.top();
        else return -inf;
    }
    int query12(){
        int first = query1();
        if(first == -inf) return -inf;
        a.pop();
        int second = query1();
        a.push(first);
        return first + second;
    }
};
Natho_nA dis_to_up[maxn + 5] , max_in_every_son[maxn + 5] , all;

至此所有都完成啦。

代码

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#define mp make_pair
#define fo(i , x , y) for(int i = x ; i <= y ; i++)
#define go(i , x , y) for(int i = x ; i >= y ; i--)
// #define local
#ifdef local
#define debug(x) cerr << #x" : " << x << ", "
#define debugn(x) cerr << #x" : " << x << "\n"
#define debugarr(a , n); cerr << #a" : \n"; fo(i , 1 , n) cerr << a[i] << " "; cerr << "\n";
#else
#define debug(x) "n-buna bless me."
#define debugn(x) "wowaka bless me."
#define debugarr(a , n) "Nayutan bless me."
#endif
using namespace std;

const int maxn = 100000;
const int inf = maxn * 100;
int n , q , black[maxn + 5];
vector<int> e[maxn + 5];

// 重链剖分
int fa[maxn + 5] , siz[maxn + 5] , dep[maxn + 5] , top[maxn + 5] , son[maxn + 5];
void dfs1(int u , int f){
    fa[u] = f;
    dep[u] = dep[f] + 1;
    siz[u] = 1;
    for(int v : e[u]){
        if(v == f) continue;
        dfs1(v , u);
        siz[u] += siz[v];
        if(siz[son[u]] < siz[v]) son[u] = v;
    }
}
void dfs2(int u , int f , int tp){
    top[u] = tp;
    if(son[u]) dfs2(son[u] , u , tp);
    for(int v : e[u]){
        if(v == f or v == son[u]) continue;
        dfs2(v , u , v);
    }
}
int lca(int u , int v){
    while(top[u] != top[v]){
        if(dep[top[u]] < dep[top[v]]) swap(u , v);
        u = fa[top[u]];
    }
    if(dep[u] > dep[v]) swap(u , v);
    return u;
}
int dis(int u , int v){
    int f = lca(u , v);
    return dep[u] - dep[f] + dep[v] - dep[f];
}

// 建点分树
int up[maxn + 5];
bool vis[maxn + 5];
int get_root(int u , int sum , int fa){
    int ans = -1;
    siz[u] = 1; // 这里的 siz 数组用的是重链剖分的,初始化过不用担心
    bool flag = true;
    for(int v : e[u]){
        if(v == fa) continue;
        if(vis[v]) continue;
        int res = get_root(v , sum , u);
        if(res != -1) ans = res;
        if(siz[v] * 2 > sum) flag = false;
        siz[u] += siz[v];
    }
    if(ans != -1) return ans;
    if(flag and (sum - siz[u]) * 2 <= sum) return u;
    return -1;
}
void solve(int rt , int sum){
    vis[rt] = true;
    for(int v : e[rt]){
        if(vis[v]) continue;
        int sumv = (siz[v] < siz[rt] ? siz[v] : sum - siz[rt]);
        int w = get_root(v , sumv , 0);
        up[w] = rt;
        solve(w , sumv);
    }
}
struct Natho_nA{
    priority_queue<int> a , del;
    void push(int x){
        a.push(x);
    }
    void remove(int x){
        del.push(x);
    }
    int query1(){
        while(a.size() and del.size() and a.top() == del.top()){
            a.pop() , del.pop();
        }
        if(a.size()) return a.top();
        else return -inf;
    }
    int query12(){
        int first = query1();
        if(first == -inf) return -inf;
        a.pop();
        int second = query1();
        a.push(first);
        return first + second;
    }
};
Natho_nA dis_to_up[maxn + 5] , max_in_every_son[maxn + 5] , all;
int main(){
    ios :: sync_with_stdio(0) , cin.tie(0) , cout.tie(0);
    // freopen(".in" , "r" , stdin);
    // freopen(".out" , "w" , stdout);
    cin >> n;
    fo(i , 1 , n - 1){
        int u , v;
        cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(1 , 0);
    dfs2(1 , 0 , 1);
    solve(get_root(1 , n , 0) , n);
    fo(u , 1 , n){
        int cur = u;
        while(up[cur]){
            dis_to_up[cur].push(dis(up[cur] , u));
            cur = up[cur];
        }
    }
    fo(u , 1 , n){
        max_in_every_son[u].push(0);
        if(up[u] != 0)
            max_in_every_son[up[u]].push(dis_to_up[u].query1());
    }
    fo(u , 1 , n){
        all.push(max_in_every_son[u].query12());
    }
    fo(u , 1 , n) black[u] = 1;
    cin >> q;
    while(q--){
        int u;
        cin >> u;
        if(black[u] == 1){
            black[u] = 0;
            int pre1 = max_in_every_son[u].query12();
            max_in_every_son[u].remove(0);
            int now1 = max_in_every_son[u].query12();
            all.remove(pre1) , all.push(now1);
            int cur = u;
            while(up[cur]){
                int pre = dis_to_up[cur].query1();
                dis_to_up[cur].remove(dis(up[cur] , u));
                int now = dis_to_up[cur].query1();
                int pre1 = max_in_every_son[up[cur]].query12();
                max_in_every_son[up[cur]].remove(pre);
                max_in_every_son[up[cur]].push(now);
                int now1 = max_in_every_son[up[cur]].query12();
                all.remove(pre1) , all.push(now1);
                cur = up[cur];
            }
        }
        else{
            black[u] = 1;
            int pre1 = max_in_every_son[u].query12();
            max_in_every_son[u].push(0);
            int now1 = max_in_every_son[u].query12();
            all.remove(pre1) , all.push(now1);
            int cur = u;
            while(up[cur]){
                int pre = dis_to_up[cur].query1();
                dis_to_up[cur].push(dis(up[cur] , u));
                int now = dis_to_up[cur].query1();
                int pre1 = max_in_every_son[up[cur]].query12();
                max_in_every_son[up[cur]].remove(pre);
                max_in_every_son[up[cur]].push(now);
                int now1 = max_in_every_son[up[cur]].query12();
                all.remove(pre1) , all.push(now1);
                cur = up[cur];
            }
        }
        cout << all.query1() << "\n";
    }
}

最后

原谅我不会起变量名喵,\(dis\_to\_up\)\(max\_in\_every\_son\) 都太长了。

赛时肯定优先写线段树分治吧。不过我没想到就是了。点分树代码长还不好想。

下面是两道原题。

P2056

P4115 话说这道题边权可以是负的,好像就不能用线段树分治做了。点分治还是能做的。


文章来源:https://www.cnblogs.com/NathonA/p/20403812
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云