CF1709E XOR Tree 题解

XOR Tree

TAGS树上启发式合并 + 异或 + 贪心

ESTIMATION:非常好的启发式合并题目

First.如何去除 0 路径

对于一条路径 uv,要使其不为 0 肯定是将 LCA(u,v) 变为 230+x 最好,这样异或值的第 30+x 位一定为 1(因为 ai230),修改之后,u,vLCA(u,v) 为根的子树内的路径都一定不为 0 了。显然,这样是最优的。

Second.如何快速判断一颗子树下有无 0 路径

首先,对于 uv 的路径权值 disu,v 可以化为 disu,1disv,1alca(u,v)

其实知道这个就可以去做 P4551 了。

这个时候可以用一个 set 储存该子树所有 disu,1 的值。然后枚举一个 v 查找有无 disv,1au 在其中,如果有,那么他们异或起来为 0,就是有 0 路径。

然后就是把这个子树删除,set 合并到它的父亲上。

这样,大体思路就出来了,暴力的话时间复杂度 O(n2logn)(枚举节点的 n2 和 set 的 logn)。

Third.优化时间复杂度

启发式合并,每次将大小小一些的集合合并到大一些的集合上,执行 logn 次,节点数就会达到 n。所以只会合并 logn 次。

总时间复杂度为 O(nlog2n)

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n;
vector<int> G[N];
int a[N], dis[N];
void dfs(int u, int fa) {
    dis[u] = a[u];
    if(fa) dis[u] ^= dis[fa];
    for (auto v : G[u]) {
        if(v != fa) dfs(v, u);
    }
}
set<int> s[N]; // 快速查找有无 0 路径
void move (int u, int v) { // 合并
    for (auto x : s[u]) s[v].insert(x);
    s[u].clear();
}
int ans = 0;
void dfs2(int u, int fa) {
    bool mark = 0;
    s[u].insert(dis[u]);
    for (auto v : G[u]) {
        if(v != fa) {
            dfs2(v, u);
            if(s[u].size() < s[v].size()) swap(s[u], s[v]); // 保证是小的合并至大的里面【启发式合并】
            for (auto x : s[v]) mark |= s[u].count(x ^ a[u]); // 如果存在 dis[u] = x ^ a[u] 说明子树中存在异或路径为 0 的路径
            move(v, u);
        }
    }
    if(mark) ans ++, s[u].clear(); // 如果存在,那么该节点需要删除
}

signed main() {
    ios::sync_with_stdio(0);
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i < n; i ++) {
        int u, v;
        cin >> u >> v, G[u].push_back(v), G[v].push_back(u);
    }
    dfs(1, 0);
    dfs2(1, 0);
    cout << ans << endl;
    return 0;
}
posted @   固态H2O  阅读(15)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示