POJ 1741 Tree (树分治模板题)
来源:互联网 发布:哪个软件可以看英剧 编辑:程序博客网 时间:2024/05/17 07:25
点击打开链接
题意:给出一棵树,求出两点之间距离小于等于k的点对数目。
解法:树的考查十分常见,原因就在于树有一般图不具备的约束条件。
如果这是一个一般图,我们想的是,做一次LCA,然后暴力两个点,看是否符合,复杂度达到O(n²),对于题目有1e4个点而且还是多组输入显然是无法接受的。
这时候我们就要想到树分治的思想。
求两个点的距离是否小于等于k,可以分为两种情况。
我们假定一个根节点为root,那么两个点距离小于等于k,这条路有可能经过root,也有可能不经过root。
对于上面两种情况。
1.如果经过root,那就简单了,我们直接求出root到每个点的距离,排序,然后O(n)扫一遍就可以知道有多少个点是符合答案的了。
比如样例中,假设root = 1,那么就可以得到1 2 2 3这些距离,令l = 0, r = 3,O(n)扫一遍,得到的答案是1 2,1 2,1 3,2 2。所以有四个点对是符合条件的。但是事实上,节点3到节点5是没有经过root的,所以我们需要把这种情况给去掉(从root枚举连接的点v,走他的子树,初始化v的距离为 root到v的距离,找出仍然符合条件的数目,具体看代码),所以经过root=1的点对符合条件的是三种。
2.如果不经过root,其实每条路肯定会经过某个点的,所以直接从root递归下去指定新的root即可。
如果root选定得不好,很容易使得复杂度退化为n²,所以树的分治是有求重心的部分在里面的,令重心为root,可以保证树的深度尽量小。
时间复杂度O(nlog²n)。复杂度证明可以搜《分治算法在树的路径问题中的应用》。
代码如下:
#include<iostream>#include<cstdio>#include<cstring>#include<cstdlib>#include<cmath>#include<ctime>#include<algorithm>using namespace std;const int maxn = 1e4 + 5;const int INF = 0x3f3f3f3f;int tot, head[maxn], to[maxn << 1], cost[maxn << 1], nx[maxn << 1];int n, k, root, sum, ans, num;//sum存放子树点数 int f[maxn], vis[maxn], son[maxn], deep[maxn];//f存点i的最大的子树节点数目 void add_edge(int u,int v,int val) { to[tot] = v, cost[tot] = val, nx[tot] = head[u], head[u] = tot++; swap(u, v); to[tot] = v, cost[tot] = val, nx[tot] = head[u], head[u] = tot++;}//找树的重心 void getroot(int u, int fa) { son[u] = 1;f[u] = 0; for(int i = head[u]; ~i; i = nx[i]) { int v = to[i]; if(v == fa || vis[v]) continue; getroot(v, u); son[u] += son[v]; //记录子树有多少个节点 f[u] = max(f[u], son[v]); } f[u] = max(f[u], sum - son[u]); if(f[u] < f[root])root = u;}//计算每个点到树的重心的距离存于deep中//deep[0]表示有多少个元素 void getdeep(int u, int fa, int last_dis) { for(int i = head[u]; ~i; i = nx[i]) { int v = to[i]; if(v == fa || vis[v])continue; deep[num++] = last_dis + cost[i]; getdeep(v, u, deep[num - 1]); }}//计算有多少个点对长度小于等于k int cal(int u, int val) {num = 0;deep[num++] = val; getdeep(u, 0, deep[num - 1]); sort(deep, deep + num); int l = 0, r = num - 1, tmp = 0; while(l < r) { //扫一遍即可 if(deep[l] + deep[r] <= k) { //如果成立,那么 tmp += r - l;//以l为首到r的两个点之间都会成立 l++;} else //否则收缩最大的范围 r--; } return tmp;}void solve(int u) { ans += cal(u, 0);//计算答案 vis[u] = 1; for(int i = head[u]; ~i; i = nx[i]) { int v = to[i]; if(vis[v])continue; ans -= cal(v, cost[i]);//计算不符合题意的答案 sum = son[v]; root = 0; getroot(v, 0); //找出子树的重心 solve(root); //递归解决 }}int main() {#ifndef ONLINE_JUDGEfreopen("in.txt", "r", stdin);#endif while(scanf("%d%d", &n, &k) != EOF) { if(n == 0 && k == 0)break; ans = 0, root = 0, tot = 0; memset(vis, 0, sizeof(vis)); memset(head, -1, sizeof(head)); for(int i = 1, u, v, val; i < n; i++) { scanf("%d%d%d", &u, &v, &val); add_edge(u, v, val); } f[0] = INF;//root=0,用来比较找出子树节点最小的重心 sum = n; getroot(1, 0); solve(root); printf("%d\n", ans); } return 0;}
阅读全文
0 0
- POJ 1741 Tree (树分治模板题)
- 树分治(点分治模板)poj-1741 Tree
- 树分治(点分治模板)poj-1741 Tree
- 树分治(点分治模板)poj-1741 Tree
- 【poj 1741】Tree 【树分治 点分治入门题】
- poj 1741 Tree 树分治
- poj 1741 Tree | 树分治
- poj 1741 Tree 树分治
- 【树分治】 POJ 1741 Tree
- 【POJ 1741】Tree(树分治)
- poj 1741 - Tree 树分治
- [POJ 1741]Tree[树分治]
- POJ 1741 Tree【树分治】
- 【树分治】 POJ 1741 Tree
- poj 1741 Tree(树分治)
- POJ:1741 Tree(男人八题-树分治)
- POJ 1741 Tree(树分治|ltc男人八题)
- POJ 1741 Tree 树的分治
- hiho 1599 dfs乱搞 [Offer收割]编程练习赛29 Problem A 逃离迷宫4
- C++子类对象隐藏了父类的同名成员函数(隐藏篇)
- 图论分类讨论 bzoj2503相框
- Eclipse编译时保留方法的形参<转>
- ConCurrent包下工具类-CyclicBarrier
- POJ 1741 Tree (树分治模板题)
- 大学生职业生涯规划
- Non-ASCII character '\xb3' in file e:\python\ship.py on line 6, but no encoding declared;
- POJ3169(差分约束系统)
- 关于C++子类父类成员函数的覆盖和隐藏
- woj 1124 最大流
- PAT 甲级 1058. A+B in Hogwarts (20)
- 编写函数,获取两段字符串的最长公共子串的长度
- Java中的策略模式