AT_apc001_f XOR Tree 题解

题意简述

给一棵有 \(N\) 个节点的树,节点编号从 \(0\)\(N-1\)
树边编号从 \(1\)\(N-1\)。第 \(i\) 条边连接节点 \(x_i\)\(y_i\),其权值为 \(a_i\)

你可以对树执行任意次操作,每次操作选取一条链和一个非负整数 \(x\),将链上的边的权值与 \(x\) 异或成为该边的新权值。

问最少需要多少次操作,使得所有边的权值都为 \(0\)

题目分析

感觉很典的东西,但是我不会,怎么回事呢?

如果直接操作边权,题目就很不可做,因为改一条链太麻烦了。

我们考虑,在修改一条链的边权的过程中,有什么东西的变化量是很少的呢?

一点也不显然,就是一个点邻接的所有边的权值异或和。一条链上除端点外所有点度数都是 \(2\),所以一条链异或一个数 \(x\) 直接等价于将任意两个点的权值异或 \(x\)。这样就简单多了。而且这两个东西也是一一对应的(剥叶子的方式互相转化)。

下面就是暴力的部分了。

首先权值只有 \(16\) 种,每种两两消去肯定不劣,这么做完之后每种最多只剩一个了。就可以状压。设 \(f_S\) 为将 \(S\) 这个集合消成 \(0\) 至少要多少次操作。首先异或和不为 \(0\)\(f_S\)\(\infty\),否则 \(f_S\) 至多为 \(\left|S\right| - 1\)。或者 \(f_S = f_T + f_{S/T}(T \in S)\)

直接枚举子集,时间复杂度 \(O(n + 3^V)\)

#include<bits/stdc++.h>
#define pii std::pair<int,int>
#define mkp std::make_pair
#define fir first
#define sec second
typedef long long ll;
inline void rd(){}
template<typename T,typename ...U>
inline void rd(T &x,U &...args){
    int ch=getchar();
    T f=1;x=0;
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x*=f;rd(args...);
}
const int N=1e5+5,V=20;
int n,a[N],k[V],m,b[V],f[1<<16],ans;
signed main(){
    rd(n);
    for(int i=1;i<n;i++){
        int x,y,z;rd(x,y,z);++x,++y;
        a[x]^=z,a[y]^=z;
    }
    for(int i=1;i<=n;i++){
        if(k[a[i]]&&a[i])++ans;
        k[a[i]]^=1;
    }
    for(int i=0;i<16;i++)if(k[i])b[++m]=i;
    memset(f,0x3f,sizeof f);f[0]=0;
    for(int t=1;t<(1<<m);t++){
        int xors=0;
        for(int i=1;i<=m;i++)if(t>>(i-1)&1)xors^=b[i];
        if(xors==0)f[t]=__builtin_popcount(t)-1;
        for(int j=(t-1)&t;j;j=(j-1)&t)f[t]=std::min(f[t],f[j]+f[t^j]);
    }
    printf("%d\n",ans+f[(1<<m)-1]);
    return 0;
}
posted @ 2025-02-08 23:11  KIreteria  阅读(6)  评论(0编辑  收藏  举报