ZOJ 3863 Paths on the Tree 树分治

题目链接:点击打开链接

题意:

给定n个点的树。 常量k

问:对于一对路径,如果公共点<=k则为合法。

问有多少个合法的路径。

{1-3, 2-4} 和 {2-4,1-3} 视为2个不同的路径对。

1-3, 3-1视为相同路径。

思路:

首先来得到一个O(n^3)的算法:

把问题转成=> 总方案数 - 公共点>k个的路径对数

显然公共点是连续的,所以公共点会组成一条路径,我们设为 x-y,则枚举x和y,就能得到公共的部分(当然要保证x-y的公共点数>k)

那么现在的问题是 以公共路径为x-y 的路径对有多少条。

x有很多子树: x1, x2, x3 ···xi 图中为(1, 3, 3) 设sumx = x_1 + x_2 + ··+ x_i ( 这里sumx = 7

y有很多子树: y1, y2, y3···yi  图中为(1, 3, 1) 设sumy = y_1 + y_2 + ··+ y_i ( 这里sumy = 5

在x子树中选2个点排列的方案数 ans_x = (sum_x  - x_i) * x_i (for any i) + (sum_x-1)

(为何加上sum_x-1, 因为不同子树间的方案已经计算过2次,但一个点是x,另一点是子树节点的方案只计算了一次, 所以+ x_1 + x_2 +···+x_i = sum_x-1)

再看 (sumx - xi) * xi

等会儿补上。。

/*
by:http://blog.csdn.net/acmmmm
*/
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <stack>
#include <time.h>
#include <queue>
template <class T>
inline bool rd(T &ret) {
	char c; int sgn;
	if (c = getchar(), c == EOF) return 0;
	while (c != '-' && (c<'0' || c>'9')) c = getchar();
	sgn = (c == '-') ? -1 : 1;
	ret = (c == '-') ? 0 : (c - '0');
	while (c = getchar(), c >= '0'&&c <= '9') ret = ret * 10 + (c - '0');
	ret *= sgn;
	return 1;
}
template <class T>
inline void pt(T x) {
	if (x <0) {
		putchar('-');
		x = -x;
	}
	if (x>9) pt(x / 10);
	putchar(x % 10 + '0');
}
using namespace std;
typedef unsigned long long ll;
const int N = 100005;

struct Edge{
	int from, to, nex;
}edge[N << 1];
int head[N], edgenum;
void add(int u, int v){ Edge E = { u, v, head[u] }; edge[edgenum] = E; head[u] = edgenum++; }
int size[N], parent[N];
void dfs_init(int u, int fa){
	size[u] = 1; parent[u] = fa;
	for (int i = head[u]; ~i; i = edge[i].nex){
		int v = edge[i].to; if (v == fa)continue;
		dfs_init(v, u);
		size[u] += size[v];
	}
}
int n, k, maxdep;

int dp[N], num[N];//num[i]表示 以i为根的树 节点数
//树重心的定义:dp[i]表示 将i点删去后 最大联通块的点数
int root;
bool vis[N];
int siz;//** 表示当前 计算的树的节点数
int G[N], top;
void getroot(int u, int fa, int deep){//找树的重心
	dp[u] = 0; num[u] = 1;
	maxdep = max(maxdep, deep);
	for (int i = head[u]; ~i; i = edge[i].nex){
		int v = edge[i].to; if (v == fa || vis[v])continue;
		getroot(v, u, deep + 1);
		num[u] += num[v];
		dp[u] = max(dp[u], num[v]);
	}
	dp[u] = max(dp[u], siz - num[u]);
	if (dp[u] < dp[root])root = u;
}

ll ans, sum[2][N], w[N];
int dep[N];
ll Siz(int u, int v){
	if (v == parent[u])return size[u];
	else return n - size[v] ;
}
void dfs(int u, int fa, int deep){
	dep[u] = deep; maxdep = max(maxdep, deep);
	w[u] = Siz(u, fa) * (Siz(u, fa) - 1);
	num[u] = 1;
	G[top++] = u;
	for (int i = head[u]; ~i; i = edge[i].nex){
		int v = edge[i].to; if (v == fa)continue;
		w[u] -= Siz(v, u) * Siz(v, u);
		if (vis[v])continue;
		dfs(v, u, deep + 1);
		num[u] += num[v];
	}
	w[u] += Siz(u, fa);
}
void work(int u){
	siz = num[u];
	root = maxdep = 0;
	getroot(u, u, 0);
	if (maxdep * 2 < k)return;
	int old = 1, cur = 0;
	fill(sum[cur], sum[cur] + maxdep + 10, 0);
	sum[cur][0] = 1;
	ll all = n, fang = 0;
	for (int i = head[root]; ~i; i = edge[i].nex){
		int v = edge[i].to;
		fang += Siz(v, root) * Siz(v, root);
	}

	for (int i = head[root], j; ~i; i = edge[i].nex){
		int V = edge[i].to; if (vis[V])continue;
		top = 0;
		dfs(V, root, 1);
		swap(old, cur);
		fill(sum[cur], sum[cur] + maxdep + 10, 0);

		for (j = 0; j < top; j++) sum[cur][dep[G[j]]] += w[G[j]];
		for (j = 0; j <= maxdep; j++)
		{
			if (k-j <= maxdep)
			ans += sum[cur][j] * sum[old][max(0, k-j)];
		}
		for (j = maxdep-1; j >= 0; j--) sum[cur][j] += sum[cur][j + 1];
		if (k <= maxdep)
			ans += sum[cur][k] * (all - Siz(V, root) - 1 + (all - Siz(V, root)) * (all - Siz(V, root) - 1) - (fang - Siz(V, root)*Siz(V, root)));
		for (j = maxdep; j >= 0; j--)sum[cur][j] += sum[old][j];
	}
	vis[root] = true;
	for (int i = head[root]; ~i; i = edge[i].nex)
	if (false == vis[edge[i].to]) work(edge[i].to);
}

int main(){
	dp[0] = N;
	int T; rd(T);
	while (T--){
		rd(n); rd(k);
		memset(head, -1, sizeof head); edgenum = 0;
		for (int i = 1, u, v; i < n; i++){
			rd(u); rd(v); add(u, v); add(v, u);
		}
		dfs_init(1, 1);
		ans = 0;
		num[1] = n;
		memset(vis, 0, sizeof vis);
		work(1);
		ll all = (ll)n*(n + 1) / 2;
		cout << (all * all - ans) << endl;
	}
	return 0;
}
/*
991

6 3
1 2
2 3
2 4
1 5
5 6

4 1
1 2
2 3
3 4

6 1
1 2
1 3
1 4
2 5
5 6

6 1
1 2
1 3
1 4
3 5
3 6

5 1
1 2
1 3
3 4
4 5

6 2
1 2
2 3
3 4
3 5
4 6

3 2
1 2
1 3

5 1
1 2
1 3
1 4
2 5

*/
时间: 2024-10-18 02:33:08

ZOJ 3863 Paths on the Tree 树分治的相关文章

ZOJ 3863 Paths on the Tree

Description Edward has a tree with n vertices conveniently labeled with 1,2,-,n. Edward finds a pair of paths on the tree which share no more than k common vertices. Now Edward is interested in the number of such ordered pairs of paths. Note that pat

poj 1744 tree 树分治

Tree Time Limit: 1000MS   Memory Limit: 30000K       Description Give a tree with n vertices,each edge has a length(positive integer less than 1001). Define dist(u,v)=The min distance between node u and v. Give an integer k,for every pair (u,v) of ve

【BZOJ-1468】Tree 树分治

1468: Tree Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 1025  Solved: 534[Submit][Status][Discuss] Description 给你一棵TREE,以及这棵树上边的距离.问有多少对点它们两者间的距离小于等于K Input N(n<=40000) 接下来n-1行边描述管道,按照题目中写的输入 接下来是k Output 一行,有多少对点之间的距离小于等于k Sample Input 7 1 6 13 6

hdu 4912 Paths on the tree(树链剖分+贪心)

题目链接:hdu 4912 Paths on the tree 题目大意:给定一棵树,和若干个通道,要求尽量选出多的通道,并且两两通道不想交. 解题思路:用树链剖分求LCA,然后根据通道两端节点的LCA深度排序,从深度最大优先选,判断两个节点均没被标 记即为可选通道.每次选完通道,将该通道LCA以下点全部标记. #pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #include

HDU 4812 D Tree 树分治+逆元处理

D Tree Problem Description There is a skyscraping tree standing on the playground of Nanjing University of Science and Technology. On each branch of the tree is an integer (The tree can be treated as a connected graph with N vertices, while each bran

hdu 4912 Paths on the tree(树链拆分+贪婪)

题目链接:hdu 4912 Paths on the tree 题目大意:给定一棵树,和若干个通道.要求尽量选出多的通道,而且两两通道不想交. 解题思路:用树链剖分求LCA,然后依据通道两端节点的LCA深度排序,从深度最大优先选.推断两个节点均没被标 记即为可选通道. 每次选完通道.将该通道LCA下面点所有标记. #pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #includ

【POJ1741】Tree 树分治 模板咯?

广告: #include <stdio.h> int main() { puts("转载请注明出处[vmurder]谢谢"); puts("网址:blog.csdn.net/vmurder/article/details/44302921"); } 题意: 给你一棵无根树,求有多少点对之间距离<=K. 题解: 树分治. 然后对于一个重心X,我们把它的所有子树中的所有点存到结构体数组中. 结构体中存距离和子树编号. 第一遍sort,我们双指针扫哪些点

HDU 4812 D Tree 树分治+逆元+hash新姿势

题意: 给定n个点的树 K 下面n个数是点权 下面n-1行给出树边. 问: 是否存在一条路径使得路径上点权积 % mod  = K 若存在则输出路径的两端. 若存在多条路径则输出字典序最小的一条. 思路: 按树重心分治. 分成路径是否经过树重心. 然后用力码.. has[x] = u; 表示乘积为x 对应的点是u 但这样has就不能用计数器来优化清空. 所以用2个数组: has[x] = cnt; has_id[x] = u; 这样has里存的是乘积为x是否存在.has_id[x] 来记录点.

POJ 1741 Tree 树分治(点分治)

题意:给你一颗带边权树,问你其中 dis(v,u) <= k 的对数 解题思路: 首先推荐大家看 09年国家集训队漆子超 的论文 看到这题  我们可以有三种思路 第一种是枚举起点,遍历整颗树找对数    时间复杂度 为  O(n^2),空间复杂度为 O(n) 第二种是树形dp的思想     每个节点用 长度为 K 数组维护 ,递归求解  时间复杂度为 O(n ^k)空间复杂度 为 O(n) 第三种就是我们要用到的点分治的思想. 这种思想的具体思路是  先找到一个  根  对这个根进行 深搜, 找