P4178&POJ1741 Tree point divide and conquer template question

Insert picture description here
The point divide and conquer algorithm is an extremely effective algorithm for path statistics on the tree. When asked to solve problems such as the path on the tree, we can think about whether we can solve it with point divide and conquer.
Divide and conquer is a recursive problem-solving method that disassembles a problem into several sub-problems, and divide and conquer on the tree is affected by the shape of the tree.

Insert picture description here
We have seen that the improper selection of the root node will cause the depth of a tree to be too large, and the time complexity is also extremely high. Therefore, we need to select a suitable point to minimize the largest subtree. The point that meets this condition is called the center of gravity of the tree, which can be obtained by a 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];
}

Correspondingly, we must also obtain the distance value of each node on the subtree with the current node as the root node to the root node

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 ;
}

Next is the core function. Because in this topic, when we directly count the root node at the beginning, there will be duplicate statistics, so we need to eliminate these duplicate parts in the following statistics. Therefore, the value of the solve function len will change. Sorting the dis array using the double pointer method can find out how many pairs of paths satisfy this condition.
It can be seen that in the divide and conquer function, the len of the initial solve is different, because we start from the root node of the subtree, so we need to add the distance from the root node to the root node of the subtree to count the answer

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 ;
}

Generally speaking, the specific content of the three functions of the point divide and conquer problem is to find the center of gravity, calculate the distance, and divide and conquer the solution. The key is the solve function (calculation function). It depends on the problem and requires specific analysis.

Complete code:


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;
}

Guess you like

Origin blog.csdn.net/weixin_45672411/article/details/108508678