[POJ1741]Tree(点分治模板)

传送门

良心解析

其实以前在求某段序列上的区间统计问题时就碰到过类似于这样的思想。

当时的区间统计问题思路大致是这样:

选取一个点作为中间点,从这个点的左边和右边统计出满足条件的点对。然后当前的中间点就可以删去了,接着递归统计左右两个区间的方案数。

其实这就是个分治和分类讨论的思想。

满足要求的解无非就是这两种:

1.区间包含中间点(也就是两端点在中间点的左右两边)

2.区间不包含中间点(也就是两个端点都在中间点的左边或右边)

所以正确性显而易见

淀粉质就是把这种思想应用于树上的路径统计上,选取一个点作为根,然后统计经过这个点的路径数,端点一定是在子树中的,

不过这会遇到一个问题,就是两个端点会在同一颗子树中,这就需要再统计子树中的答案再减去。接着再递归求解每一颗子树。

最后一个问题就是根节点的选取,需要选重心,防止出现链导致时间复杂度退化的情况。

其次是统计答案的方法,对于此题来说有个神奇的nlogn的方法,上面的链接中已经给出。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 100001
#define INF ~(1 << 31)

using namespace std;

int n, K, cnt, root, tot, ans;
int head[N], to[N], nex[N], val[N], f[N], size[N], deep[N], d[N];
bool vis[N];
//f[i]表示节点i的最大子树的大小
//deep[i]表示节点i到根的距离 
//vis[i] == 1 表示该节点删除 

inline int read()
{
	int x = 0, f = 1;
	char ch = getchar();
	for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
	for(; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
	return x * f;
}

inline void add(int x, int y, int z)
{
	to[cnt] = y;
	val[cnt] = z;
	nex[cnt] = head[x];
	head[x] = cnt++;
}

//获取当前块的重心 
inline void getroot(int u, int fa)
{
	int i, v;
	f[u] = 0;
	size[u] = 1;
	for(i = head[u]; ~i; i = nex[i])
	{
		v = to[i];
		if(v == fa || vis[v]) continue;
		getroot(v, u);
		size[u] += size[v];
		f[u] = max(f[u], size[v]);
	}
	f[u] = max(f[u], tot - size[u]);
	if(f[u] < f[root]) root = u;
}

//获取当前块中所有的点到根的距离 
inline void getdeep(int u, int fa)
{
	int i, v;
	deep[++deep[0]] = d[u];
	for(i = head[u]; ~i; i = nex[i])
	{
		v = to[i];
		if(v == fa || vis[v]) continue;
		d[v] = d[u] + val[i];
		getdeep(v, u);
	}
}

//求解经过当前根的满足条件的路径的方案数 
inline int cal(int u, int now)
{
	int l, r, ret = 0; 
	d[u] = now;
	deep[0] = 0;
	getdeep(u, 0);
	sort(deep + 1, deep + deep[0] + 1);
	l = 1, r = deep[0];
	while(l < r)
		if(deep[l] + deep[r] <= K) ret += r - l++;
		else r--;
	return ret;
}

//递归求解每一块 
inline void work(int u)
{
	int i, v;
	vis[u] = 1;
	ans += cal(u, 0);
	for(i = head[u]; ~i; i = nex[i])
	{
		v = to[i];
		if(vis[v]) continue;
		//因为在求解的时候会遇到这种情况:经过当前根的满足条件的路径的两端点在同一颗子树中,这样的路径也会统计到答案中 
		//然而并不合法,所以需要遍历每一颗子树,减去每一颗子树中满足条件的路径 
		ans -= cal(v, val[i]);
		tot = size[v];
		root = 0;
		getroot(v, u);
		work(root);
	}
}

int main()
{
	int i, x, y, z;
	while(~scanf("%d %d", &n, &K))
	{
		if(!n && !K) break;
		cnt = root = ans = 0;
		memset(vis, 0, sizeof(vis));
		memset(head, -1, sizeof(head));
		for(i = 1; i < n; i++)
		{
			x = read();
			y = read();
			z = read();
			add(x, y, z);
			add(y, x, z);
		}
		tot = n;
		f[0] = INF;
		getroot(1, 0);
		work(root);
		printf("%d
", ans);
	}
	return 0;
}

  

原文地址:https://www.cnblogs.com/zhenghaotian/p/8145261.html