2020 Niuke Summer School 5th B-Graph (árbol de expansión mínimo trie XOR)

El título está aquí. El
significado del título: Darle n bordes superiores yn bordes. Espero que pueda agregar o eliminar algunos bordes. Después de cada operación, debe asegurarse de que: 1. La imagen es Unicom 2. La imagen está en un anillo Los pesos de los bordes son XORed y son iguales a 0, lo que le permite encontrar la suma de los pesos de los bordes más pequeños que cumplen con los requisitos.
Idea:
Si queremos agregar bordes porque queremos asegurarnos de que el valor XOR es igual a 0, entonces los pesos de los bordes que agregamos deben ser Es conveniente que la suma de los pesos de las aristas entre los dos nodos originales sea la misma. Eliminar las aristas. Para asegurar que sea igual a 0, se agregará una arista con el mismo peso. De esta manera, el peso del borde entre dos puntos no cambiará, por lo que este problema se puede transformar en un peso de punto (valor OR exclusivo del nodo raíz al nodo actual), por lo que un El peso de un punto a otro punto se convierte en el valor XOR de los dos puntos. Luego, al conectar algunos bordes, se minimiza el peso del gráfico generado.
Este problema se convierte en el problema de buscar un árbol de expansión mínimo o
exclusivo : se puede implementar con el algoritmo de Boruvak, pero no lo haré, lo aprenderé más adelante.
También puede utilizar el método de dividir y conquistar de 01Trie +. Para obtener explicaciones específicas sobre el árbol de expansión mínimo de XOR, consulte este blog, que es muy claro.

Código:

#include <bits/stdc++.h>

using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
const int MAXN = 1e5+7;
int trie[MAXN*30][2],idx,a[MAXN];
int cnt,head[MAXN],vis[MAXN];
ll tot;
struct node
{
    
    
	int next,to,w;
}edge[MAXN*2];

void addedge(int u,int v,int w)
{
    
    
	edge[++cnt].to = v;
	edge[cnt].w = w;
	edge[cnt].next = head[u];
	head[u] = cnt;
}

void pre_work(int u)//根节点到当前节点的异或值
{
    
    
	vis[u] = 1;
	for(int i = head[u];i != 0;i = edge[i].next){
    
    
		int v = edge[i].to;
		if(!vis[v]){
    
    
			a[v] = a[u]^edge[i].w;
			pre_work(v); 
		}
	}
}

void insert(int x)
{
    
    
	int p = 0;
	for(int i = 29;i >= 0;i --){
    
    
		int u = x>>i & 1;
		if(!trie[p][u]) trie[p][u] = ++idx;
		p = trie[p][u];
	}
}

int query(int x)
{
    
    
	int p = 0,ans = 0;
	for(int i = 29;i >= 0;i --){
    
    
		int u = x>>i & 1;
		if(trie[p][u]){
    
    //先走异或值小的
			p = trie[p][u];
		}
		else{
    
    
			ans += (1<<i);
			p = trie[p][!u];
		}
	}
	return ans;
}

void dfs(int l,int r,int depth)
{
    
    
	if(depth == -1 || l >=r ) return ;
	int mid = l-1;
	while(mid < r && ((a[mid+1] >> depth) & 1) == 0) mid++;//一直走到找到分开的那个点 分成左右两个集合再开始分治
	
	dfs(l,mid,depth-1);
	dfs(mid+1,r,depth-1);
	if(mid == l-1 || mid == r) return ;//走到单点
	for(int i = l;i <= mid;i ++){
    
    
		insert(a[i]);//左边集合插入 右边找
	}
	int num = inf;
	for(int i = mid+1;i <= r;i ++){
    
    
		num = min(num,query(a[i]));// 找到最小的异或值
		//printf("%d\n",temp);
	}
	tot += (ll)num;
	//清空trie树
	for(int i = 0;i <= idx;i ++){
    
    
		trie[i][0] = trie[i][1] = 0;
	}
	idx = 0;
}

int main()
{
    
    
	int n;
	scanf("%d",&n);
	for(int i = 1;i <= n-1;i ++){
    
    
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		u++,v++;
		addedge(u,v,w);
		addedge(v,u,w);
	}
	a[1] = 0;
	pre_work(1);
	//for(int i = 1;i <= n;i ++)printf("%d ",a[i]);
	//printf("\n");
	sort(a+1,a+1+n);//保证分治是 两个区间的正确性
	dfs(1,n,29);
	printf("%lld\n",tot);
	return 0;
}

Supongo que te gusta

Origin blog.csdn.net/weixin_45672411/article/details/107688087
Recomendado
Clasificación