P4178 y POJ1741 Pregunta de plantilla de división y conquista de puntos de árbol

Inserte la descripción de la imagen aquí
El algoritmo de divide y vencerás de puntos es un algoritmo extremadamente eficaz para las estadísticas de ruta en el árbol. Cuando se nos pida que resuelva problemas como el camino en el árbol, podemos pensar si podemos resolverlo con dividir y conquistar.
Dividir y conquistar es un método recursivo de resolución de problemas que desarma un problema en varios subproblemas, y dividir y conquistar en el árbol se ve afectado por la forma del árbol.

Inserte la descripción de la imagen aquí
Hemos visto que la selección incorrecta del nodo raíz hará que la profundidad de un árbol sea demasiado grande y la complejidad del tiempo también sea extremadamente alta, por lo que debemos seleccionar un punto adecuado para minimizar el subárbol más grande. El punto que cumple con esta condición se llama centro de gravedad del árbol, que se puede obtener mediante un dfs

int get_root(int u,int fa)//找出树的重心
{
    
    //siz[i]是指以i为根子树的大小,maxson[i]是指以i为根最大的子树的大小
	//SIZE 为当前处理的这棵树的大小 maxx代表已经找到的最大子树的最小值
	siz[u] = 1;maxson[u] = 0;
	for(int i = head[u];i != 0;i = edge[i].next){
    
    
		int v = edge[i].to;
		if(vis[v] || v == fa) continue;
		get_root(v,u);
		siz[u] = siz[u] + siz[v];
		maxson[u] = max(maxson[u],siz[v]);
	}
	maxson[u] = max(maxson[u],SIZE-siz[u]);
	if(maxson[u] < maxx) root = u,maxx = maxson[u];
}

En consecuencia, también debemos obtener el valor de distancia de cada nodo en el subárbol con el nodo actual como nodo raíz al nodo raíz.

void get_dis(int u,int fa,int d)//从每一棵新建的子树求距离函数
{
    
    
	dis[++num] = d;//d数组保存当前根节点到每一个点的距离
	for(int i = head[u];i;i = edge[i].next){
    
    
		int v = edge[i].to;
		if(vis[v] || v == fa) continue;
		get_dis(v,u,d+edge[i].w);
	}
	return ;
}

La siguiente es la función principal. Porque en este tema, cuando contamos directamente el nodo raíz al principio, habrá estadísticas duplicadas, por lo que debemos eliminar estas partes duplicadas en las siguientes estadísticas. Por lo tanto, el valor de la función de resolución len cambiará. Ordenando la matriz dis usando el método de doble puntero puede averiguar cuántos pares de caminos satisfacen esta condición.
Se puede ver que en la función divide y vencerás, la longitud de la resolución inicial es diferente, porque partimos del nodo raíz del subárbol, por lo que necesitamos sumar la distancia desde el nodo raíz al nodo raíz del subárbol para contar la respuesta.

int calculate(int rt,int len)
{
    
    
	num = 0;
	memset(dis,0,sizeof(dis));
	get_dis(rt,0,len);//以当前子树的根节点出发 获得一下路径长度
	sort(dis+1,dis+1+num);
	int L = 1,R = num,res = 0;
	while(L <= R){
    
    //双指针法 确定答案
		if(dis[L] + dis[R] <= k){
    
    
			res += R-L;+
			L++;
		}
		else R--;
	}
	return res;
}

void Divide(int rt)//分治函数 核心部分
{
    
    
	ans = ans + calculate(rt,0);
	vis[rt] = 1;
	for(int i = head[rt];i;i = edge[i].next){
    
    //对没棵子树进行分治
		int v = edge[i].to;
		if(vis[v]) continue;
		ans = ans - calculate(v,edge[i].w);
		SIZE = siz[v];//都重新换一下 以下的信息全部重新赋一个值
		maxx = inf;
		root = 0;
		get_root(v,rt);
		Divide(root);//分治新的根节点
	}
	return ;
}

En términos generales, el contenido específico de las tres funciones del problema punto divide y vencerás es encontrar el centro de gravedad, calcular la distancia y dividir y conquistar la solución. La clave es la función resolver (función de cálculo). Depende del problema y requiere un análisis específico.

Código completo:


Problem: 1741		User: tzteyang777
Memory: 1316K		Time: 844MS
Language: G++		Result: Accepted
Source Code
//#include <bits/stdc++.h>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>

using namespace std;
typedef long long ll;
//点分治
//找到一个节点使其最大子树的大小尽量小 这时分治递归的时间复杂度是最低的
//而这样的节点 就叫做树的重心 可以用一个dfs来求O(n)的时间
const int MAXN = 1e4+7;
#define inf 0x3f3f3f3f
int head[MAXN],maxson[MAXN],vis[MAXN],siz[MAXN],dis[MAXN];
int cnt,SIZE,maxx,num,k,root;
ll ans;
struct Edge
{
    
    
	int to,next,w;
}edge[MAXN<<1];

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

int get_root(int u,int fa)//找出树的重心
{
    
    //siz[i]是指以i为根子树的大小,maxson[i]是指以i为根最大的子树的大小
	//SIZE 为当前处理的这棵树的大小 maxx代表已经找到的最大子树的最小值
	siz[u] = 1;maxson[u] = 0;
	for(int i = head[u];i != 0;i = edge[i].next){
    
    
		int v = edge[i].to;
		if(vis[v] || v == fa) continue;
		get_root(v,u);
		siz[u] = siz[u] + siz[v];
		maxson[u] = max(maxson[u],siz[v]);
	}
	maxson[u] = max(maxson[u],SIZE-siz[u]);
	if(maxson[u] < maxx) root = u,maxx = maxson[u];
}

void get_dis(int u,int fa,int d)//从每一棵新建的子树求距离函数
{
    
    
	dis[++num] = d;//d数组保存当前根节点到每一个点的距离
	for(int i = head[u];i;i = edge[i].next){
    
    
		int v = edge[i].to;
		if(vis[v] || v == fa) continue;
		get_dis(v,u,d+edge[i].w);
	}
	return ;
}

int calculate(int rt,int len)
{
    
    
	num = 0;
	memset(dis,0,sizeof(dis));
	get_dis(rt,0,len);
	sort(dis+1,dis+1+num);
	int L = 1,R = num,res = 0;
	while(L <= R){
    
    //双指针法 确定答案
		if(dis[L] + dis[R] <= k){
    
    
			res += R-L;+
			L++;
		}
		else R--;
	}
	return res;
}

void Divide(int rt)//分治函数 核心部分
{
    
    
	ans = ans + calculate(rt,0);
	vis[rt] = 1;
	for(int i = head[rt];i;i = edge[i].next){
    
    //对没棵子树进行分治
		int v = edge[i].to;
		if(vis[v]) continue;
		ans = ans - calculate(v,edge[i].w);
		SIZE = siz[v];//都重新换一下
		maxx = inf;
		root = 0;
		get_root(v,rt);
		Divide(root);//分治新的根节点
	}
	return ;
}

int main()
{
    
    
	int n;
	while(~scanf("%d%d",&n,&k)&&(n&&k)){
    
    
		cnt = 0;
		memset(head,0,sizeof(head));
		for(int i = 1;i < n;i ++){
    
    
			int a,b,c;
			scanf("%d%d%d",&a,&b,&c);
			addedge(a,b,c);
			addedge(b,a,c);
		}
		ans = 0;
		memset(vis,0,sizeof(vis));
		maxx = inf;SIZE = n;
		get_root(1,0);
		Divide(root);
		printf("%lld\n",ans);
	}
	return 0;
}

Supongo que te gusta

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