2020牛客暑期多校训练营(第五场)B Graph 异或最小生成树

题目大意
给出一颗树,有两种操作,添加一条边,删除一条边。
每个时刻必须满足
如果有环那么环的边权异或和必须是0,
必须是联通的。
解题思路
任意两个点之间连边的权值都是固定的,由于图需要始终联通,所以两点间始终存在至少一条路径,如果存在多条,根据环的异或和为0,两点间的路径的异或和应该相等,且始终是固定的。(摘自官方题解) 这样么,就可以每个点都记录一个权值,而两点间连边的权值,就是两端点权值的异或值。
接下来就是求异或最小生成树的问题了
当求一些数的最大或最小异或值,就可以联想到字典树:在字典树中两个数的最近公共祖先越近,两个数的异或值就越小

 对于树中某一节点p,p的左子树内部异或绝对优于p的左子树中的任意一点异或p的右子树中的任意一点, 右子树同理。
 在求以p为根节点的最小异或生成树生成树时,明显就是左子树内部求+右子树内部求+左右子树中的某两点互相连接成树。
 这里明显用到分治思想, 前两项可以递归算出,最后一项可以暴力枚举求出:
 以左子树为标准,枚举右子树的所有数与左子树的数异或,求一个min。右子树与之一样
 到这里还有一个问题,就是怎样知道某一个节点下的左右子树中分别有哪些数?
这里的解决方案就是首先对所有的点值排个序,这样在建树的时候它的顺序是从左到右,
用两个数组,l[i]表示数的二进制表示中包含i这个节点在序列中下标的左端点,r[i]就是右端点

代码区

#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i ++ )
#define clr(x, y) memset(x, y, sizeof (x))
#define one first
#define two second
#define LL long long
#define PII pair <int, int>
inline bool read(LL &num) {
 char in;
 bool IsN=false;
 in=getchar();
 if(in==EOF) return false;
 while(in!='-'&&(in<'0'||in>'9')) in=getchar();
 if(in=='-') {
  IsN=true;
  num=0;
 } else num=in-'0';
 while(in=getchar(),in>='0'&&in<='9') {
  num*=10,num+=in-'0';
 }
 if(IsN) num=-num;
 return true;
}
const int N = 2e5 + 7;
vector<PII> G[N];
int T[N*30][2], idx;
int a[N];
int l[N*30], r[N*30];
void add(int x, int id) {
 int p = 0;
 for (int i = 29; i >= 0; i -- ) {
  int &s = T[p][x >> i & 1];
  if (!s) {
   s = ++idx;
   l[idx] = id;
  }
  p = s;
  r[p] = id;
 }
}
LL find(int p, int pos, int x) {
 LL res = 0;
 for (int i = pos; i >= 0; i --) {
  int s = x >> i & 1;
  if (T[p][s]) p = T[p][s];
  else {
   p = T[p][!s];
   res += (1 << i);
  }
 }
 return res;
}
LL query(int p, int pos) {
 if (T[p][0] && T[p][1]) {
  int x = T[p][0], y = T[p][1];
  LL minv = 1e17;
  rep(i, l[x], r[x])
  minv = min(minv, find(y, pos - 1, a[i]) + (1 << pos));
  return minv + query(T[p][0], pos - 1) + query(T[p][1], pos - 1);
 } else if (T[p][0]) return query(T[p][0], pos - 1);
 else if (T[p][1]) return query(T[p][1], pos - 1);
 return 0;
}
void dfs(int u, int f) {
// cout << a[u] << " " << u << endl;
 rep(i, 0, G[u].size() - 1) {
//  cout << "dsadas" << endl;
  int v = G[u][i].one;
  LL w = G[u][i].two;
  if (v == f) continue;
  a[v] = a[u] ^ w;
//  cout << a[v] << " " << v << endl;
  dfs(v, u);
 }
}
int main () {
 int n;
 scanf ("%d", &n);
 rep(i, 2, n) {
  int u, v, w;
  scanf ("%d%d%d", &u, &v, &w);
  u ++, v ++;
  G[u].push_back({v, w});
  G[v].push_back({u, w});
 }
 dfs(1, -1);
 sort(a + 1, a + 1 + n);
 rep(i, 1, n) add(a[i], i);
 printf ("%lld\n", query(0, 29));
 return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值