Acwing252.树

题目分析

树中的路径分为三种

  • 路径两端在同一个子树
  • 路径两端在不同子树
  • 路径有一端是重心

因此可以分情况处理, 对于第一种情况可以进行递归处理, 第二种情况需要使用容斥原理求得(下面重点介绍), 第三种情况枚举重心到其他节点的路径就可以求得

代码分析

求子树大小

int get_size(int _index, int pre) {
// 如果当前子树被删除, 返回0
if (visited[_index]) return 0; int res = 1;
for (int i = head[_index]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
if (ver == pre) continue;
res += get_size(ver, _index);
} return res;
}

求当前点出发的子树的所有路径

void get_dist(int _index, int pre, int dis, int &ptr) {
if (visited[_index]) return;
arr2[ptr++] = dis;
for (int i = head[_index]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
if (ver == pre) continue;
get_dist(ver, _index, dis + w[i], ptr);
}
}

求树的重心

int get_weight_center(int _index, int pre, int tot, int &res) {
if (visited[_index]) return 0;
int sum = 1, max_son = 0; for (int i = head[_index]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
if (ver == pre) continue;
int size = get_weight_center(ver, _index, tot, res);
// 更新最大子树大小
max_son = max(max_son, size);
sum += size;
} // 总数减下面的子树总和, 就是当前点所在父节点的子树大小
max_son = max(max_son, tot - sum);
// 如果最大子树大小 <= tot / 2, 那么当前点可以作为重心
if (max_son <= tot >> 1) res = _index;
return sum;
}

注意:这里树的重心只要子树的大小<= n / 2就可以保证点分治时间复杂度

求单个子树中路径和 <= k的路径组合数量

int get(int arr[], int cnt) {
sort(arr, arr + cnt);
int res = 0; for (int i = cnt - 1, j = -1; i >= 0; --i) {
// 找到满足条件的最大j
while (j + 1 < i && arr[j + 1] + arr[i] <= k) j++;
j = min(j, i - 1);
res += j + 1;
} return res;
}

双指针算法求解

核心函数

int calc(int _index) {
if (visited[_index]) return 0;
int res = 0; // 找到当前子树的重心
get_weight_center(_index, -1, get_size(_index, -1), _index);
visited[_index] = true; int p1 = 0;
for (int i = head[_index]; ~i; i = next_edge[i]) {
int ver = edge_end[i], p2 = 0;
get_dist(ver, -1, w[i], p2);
// 减去一个子树中的组合情况
res -= get(arr2, p2); // 将子树的距离加入到去全局的距离中
for (int j = 0; j < p2; ++j) {
// 某个点是重心的情况
if (arr2[j] <= k) res++;
arr1[p1++] = arr2[j];
}
} // 加上子树内部的组合情况
res += get(arr1, p1);
// 递归处理子树
for (int i = head[_index]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
res += calc(ver);
}
return res;
}

先找到当前子树的重心, 然后将重心标记

然后将路径分为三种情况, 上面有论述,

直接看最复杂的情况, 也就是两个点分别在两个子树中

  • 求得子树中的路径组合, 因为是不合法的, 因此将其从答案中减去
  • 然后将子树的路径添加到全局的路径当中
  • 在添加过程中不要忘记记录子树到重心也是一种情况
  • 最后加入到res的就是全局的除去子树之间的路径组合

最后处理第一种和第三种情况

时间复杂度分析

递归过程中一共$\log{}{n}$层, 每一层点数不超过$n$, 排序时间复杂度$O(n\log_{}{n})$, 因此总的时间复杂度$O(n\log_{}{n}^{2})$

完整代码

#include <iostream>
#include <algorithm>
#include <cstring> using namespace std; const int N = 10010, EDGE_NUMBER = N << 1; int n, k;
int head[N], edge_end[EDGE_NUMBER], next_edge[EDGE_NUMBER], w[EDGE_NUMBER], edge_index;
//节点是否被删除
bool visited[N];
//当前重心的所有子树的距离, 当前子树的距离
int arr1[N], arr2[N]; void add(int ver1, int ver2, int val) {
edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], w[edge_index] = val, head[ver1] = edge_index++;
} //求子树大小
int get_size(int _index, int pre) {
// 如果当前子树被删除, 返回0
if (visited[_index]) return 0; int res = 1;
for (int i = head[_index]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
if (ver == pre) continue;
res += get_size(ver, _index);
} return res;
} //求从当前点出发所有路径的长度
void get_dist(int _index, int pre, int dis, int &ptr) {
if (visited[_index]) return;
arr2[ptr++] = dis;
for (int i = head[_index]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
if (ver == pre) continue;
get_dist(ver, _index, dis + w[i], ptr);
}
} //求树的重心, 返回值是当前子树大小
int get_weight_center(int _index, int pre, int tot, int &res) {
if (visited[_index]) return 0;
int sum = 1, max_son = 0; for (int i = head[_index]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
if (ver == pre) continue;
int size = get_weight_center(ver, _index, tot, res);
// 更新最大子树大小
max_son = max(max_son, size);
sum += size;
} // 总数减下面的子树总和, 就是当前点所在父节点的子树大小
max_son = max(max_son, tot - sum);
// 如果最大子树大小 <= tot / 2, 那么当前点可以作为重心
if (max_son <= tot >> 1) res = _index;
return sum;
} int get(int arr[], int cnt) {
sort(arr, arr + cnt);
int res = 0; for (int i = cnt - 1, j = -1; i >= 0; --i) {
// 找到满足条件的最大j
while (j + 1 < i && arr[j + 1] + arr[i] <= k) j++;
j = min(j, i - 1);
res += j + 1;
} return res;
} int calc(int _index) {
if (visited[_index]) return 0;
int res = 0; // 找到当前子树的重心
get_weight_center(_index, -1, get_size(_index, -1), _index);
visited[_index] = true; int p1 = 0;
for (int i = head[_index]; ~i; i = next_edge[i]) {
int ver = edge_end[i], p2 = 0;
get_dist(ver, -1, w[i], p2);
// 减去一个子树中的组合情况
res -= get(arr2, p2); // 将子树的距离加入到去全局的距离中
for (int j = 0; j < p2; ++j) {
// 某个点是重心的情况
if (arr2[j] <= k) res++;
arr1[p1++] = arr2[j];
}
} // 加上子树内部的组合情况
res += get(arr1, p1);
// 递归处理子树
for (int i = head[_index]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
res += calc(ver);
}
return res;
} int main() {
while (scanf("%d%d", &n, &k), n && k) {
memset(visited, false, sizeof visited);
memset(head, -1, sizeof head);
edge_index = 0; for (int i = 0; i < n - 1; ++i) {
int ver1, ver2, val;
scanf("%d%d%d", &ver1, &ver2, &val);
add(ver1, ver2, val);
add(ver2, ver1, val);
} printf("%d\n", calc(0));
}
return 0;
}

男人八题-点分治-Acwing252.树的更多相关文章

  1. poj 1741 楼教主男人八题之中的一个:树分治

    http://poj.org/problem? id=1741 Description Give a tree with n vertices,each edge has a length(posit ...

  2. Cogs 1714. [POJ1741][男人八题]树上的点对(点分治)

    [POJ1741][男人八题]树上的点对 ★★★ 输入文件:poj1741_tree.in 输出文件:poj1741_tree.out 简单对比 时间限制:1 s 内存限制:256 MB [题目描述] ...

  3. poj 1737男人八题之一 orz ltc

    这是楼教主的男人八题之一.很高兴我能做八分之一的男人了. 题目大意:求有n个顶点的连通图有多少个. 解法: 1.  用总数减去不联通的图(网上说可以,我觉得时间悬) 2.    用动态规划(数学递推) ...

  4. POJ1742 Coins(男人八题之一)

    前言 大名鼎鼎的男人八题,终于见识了... 题面 http://poj.org/problem?id=1742 分析 § 1 多重背包 这很显然是一个完全背包问题,考虑转移方程: DP[i][j]表示 ...

  5. 新男人八题---AStringGame

    终于完成进度男人1/8,为了这题学了sam= = 题意先有一个串,n个子串,两个人轮流每次在子串上加字符,要求加完后还是原串的子串,最后不能加的就是输者,求赢的人 解法:sam之后在构造的状态图上跑s ...

  6. poj 1742(好题,楼天城男人八题,混合背包)

    Coins Time Limit: 3000MS   Memory Limit: 30000K Total Submissions: 33269   Accepted: 11295 Descripti ...

  7. 博弈论(男人八题):POJ 1740 A New Stone Game

    A New Stone Game Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 5694   Accepted: 3119 ...

  8. poj 1743 男人八题之后缀数组求最长不可重叠最长重复子串

    Musical Theme Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 14874   Accepted: 5118 De ...

  9. nyoj137 取石子(三) 楼教主男人八题之一

    思路:一堆时,N态.两堆时,当两堆数量相同,P态,不同为N态.三堆时,先手可以变成两堆一样的,必胜N态. 此时可以总结规律:堆数为偶数可能且石子数都是两两相同的,为P态.分析四堆时,当四堆中两两数量一 ...

  10. 洛谷T44252 线索_分治线段树_思维题

    分治线段树,其实就是将标记永久化,到最后再统一下传所有标记. 至于先后顺序,可以给每个节点开一个时间戳. 一般地,分治线段树用于离线,只查询一次答案的题目. 本题中,标记要被下传 222 次. Cod ...

随机推荐

  1. 分布式事务之dtm

    github: https://github.com/dtm-labs/dtm 本人使用场景, 目前微服务中存在的用户服务, 商品服务,订单服务, 支付服务, 在进行下单操作的时候,需要创建订单并扣减 ...

  2. MySQL之使用pt-online-schema-change在线修改大表结构

    原因: 最近公司上一个功能, 需要为其中某个表中新增字段,但是考虑到线上数据已经达到300w+的级别,同时使用的mysql的版本是5.7而非8.0,这会导致新增字段的时候,对全表进行加锁,直到添加完毕 ...

  3. Linux之定时任务crontab

    一.cron.d增加定时任务 当我们要增加全局性的计划任务时,一种方式是直接修改/etc/crontab.但是,一般不建议这样做,/etc/cron.d目录就是为了解决这种问题而创建的. 例如,增加一 ...

  4. .net core想到哪写道哪之hello world

    今天,我们来创建一个helo world,讲一讲.Net 6最新的顶级语句的问题. 在.Net 6中最大的变化应该就是多了个顶级语句. 这玩意是个啥呢,它让C#看起来像个脚本语言了,一个Hello W ...

  5. ThreeJs-04详解材质与纹理

    一.matcap材质 这个材质不会受到光照影响,但是如果图片本身有光就可以一直渲染这个图片本来的样子,用来将一个图片纹理渲染到物体上的材质 代码实现 加载模型后,开启纹理渲染,并把它的材质变为这个材质 ...

  6. 用谷歌经典ML方法方法来设计生成式人工智能语言模型

    上一篇:<人工智能模型学习到的知识是怎样的一种存在?> 序言:在接下来的几篇中,我们将学习如何利用 TensorFlow 来生成文本.需要注意的是,我们这里并不使用当前最热门的 Trans ...

  7. 揭秘“山姆黄牛”背后的技术逻辑:用Java实现会员管理系统的防黄牛策略

    在浙江绍兴的山姆超市外,"黄牛"现象引发了广泛关注.这些"黄牛"通过提供带入和结账服务,让未办理会员卡的消费者也能进入超市购物.这一行为不仅扰乱了市场秩序,也对 ...

  8. 鸿蒙NEXT开发案例:经纬度距离计算

    [引言] 在鸿蒙NEXT平台上,我们可以轻松地开发出一个经纬度距离计算器,帮助用户快速计算两点之间的距离.本文将详细介绍如何在鸿蒙NEXT中实现这一功能,通过简单的用户界面和高效的计算逻辑,为用户提供 ...

  9. 中电金信成为开源风险评估与治理技术实验室软件物料清单(SBOM)工作组成员单位

    近期,中电金信成功加入国家工业信息安全发展研究中心开源风险评估与治理技术实验室软件物料清单(SBOM)工作组,成为其成员单位,并同步加入"筑链计划"之中. SBOM是Softwar ...

  10. 【Python】【Flask】【字符串索引】计算人民币与美元的相互计算

    目录 简介 Python Code 导包 设置首页 计算的接口 问题0:设置请求方式 问题1:关于接收数据可能存在的问题 问题2:返回结果 启动 完整代码 HTML Code 问题分析 分析:获取下拉 ...