题目大意

给一棵树,每条边有边权。可以任意加边和删边,但要满足任何时刻图连通,而且任何一个环的边权异或和为0。求操作后最小权值和。

解题思路

任意两个点之间连边的权值都是固定的,由于图需要始终联通,所以两点间始终存在至少一条路径,如果存在多条,根据环的异或和为0,两点间的路径的异或和应该相等,且始终是固定的。(摘自官方题解)

这样么,就可以每个点都记录一个权值,而两点间连边的权值,就是两端点权值的异或值。
接下来就是求异或最小生成树的问题了,使用Boruvka算法来解决是一种很吼的选择(学到了新的算法)。

Boruvka以及其他最小生成树算法:https://www.iteye.com/blog/dsqiu-1689178

而要让异或和最小,就要让二进制高位尽可能相等。
这样可以用到字典树来解决,连接两个点时,在这个节点的子树中找异或后最小的点对;为了保证得到的异或结果尽可能小,向下考虑走向时尽量方向相同。

AC代码

#include<bits/stdc++.h>
using namespace std;
struct link
{
    int x,y,z;
} e[200010];
int a[200010],b[6000010],c[6000010],d[100010],v[6000010][2],m[30],s=1,tot;
long long ans;
void get(int x,int y)
{
    for(int i=d[x];i;i=e[i].y)
        if(e[i].x!=y)
        {
            a[e[i].x]=a[x]^e[i].z;
            get(e[i].x,x);
        }
}
long long find(int x,int y)
{
    if(c[x]==30) return a[b[x]]^a[b[y]];
    bool f=0;
    long long z=2e9;
    if(v[x][0] && v[y][0]) z=min(z,find(v[x][0],v[y][0])),f=1;
    if(v[x][1] && v[y][1]) z=min(z,find(v[x][1],v[y][1])),f=1;
    if(!f)
    {
        if(v[x][0] && v[y][1]) z=min(z,find(v[x][0],v[y][1]));
        else if(v[x][1] && v[y][0]) z=min(z,find(v[x][1],v[y][0]));
    }
    return z;
}
void dfs(int x)
{
    if(!x) return;
    dfs(v[x][0]);dfs(v[x][1]);
    if(v[x][0] && v[x][1]) ans+=find(v[x][0],v[x][1]);
}
int main()
{
    int n,x,y,z,i,j;
    bool f;
    b[1]=m[0]=1;
    for(i=1;i<=29;i++)
        m[i]=m[i-1]*2;
    scanf("%d",&n);
    for(i=1;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        x++,y++;
        e[++tot].x=y,e[tot].z=z,e[tot].y=d[x],d[x]=tot;
        e[++tot].x=x,e[tot].z=z,e[tot].y=d[y],d[y]=tot;
    }
    get(1,0);
    for(i=1;i<=n;i++)
    {
        x=1;
        for(j=29;j>=0;j--)
        {
            y=1<<j,f=y&a[i];
            if(!v[x][f]) v[x][f]=++s,c[s]=29-j+1,b[s]=n+1;
            x=v[x][f];
        }
        b[x]=i;
    }
    dfs(1);
    printf("%lld\n",ans);
    return 0;
}