[SMOJ2104]树
来源:互联网 发布:网络分析仪器 编辑:程序博客网 时间:2024/06/06 00:38
60%:
做法一:直接考虑从每个结点出发,向下 dfs 考察统计。
时间复杂度(上限):
做法二:树形 DP。记
时间复杂度:
100%:
稍微结合一下做法一和做法二就可以了。
也就是说,在适当的时候用暴力,但也要能够可以把子结点的结果合并给父亲。这就要用到“树链”——启发式合并。
以样例为例,进行说明。先预处理一下,对树上的边进行轻重剖分:
这一步,跑一遍
接下来,我们抛弃做法二里面用二维数组标记的繁琐方法,现在我们只需要两个数组。用
首先,从各叶子结点开始,不断向父亲 dfs,直到遇到轻边。一开始应该从 2 出发,时间戳为
结点 2 的类型为 1,点权为 3。则
(当前时间戳变为 2)结点 4 的类型为 1,点权为 5。发现
到达结点 3,类型为 2,点权为 4。则
而 5 是轻儿子,暴力 dfs 统计,
到达根结点,类型为 1,点权为 2。发现
而 1 还有一个轻儿子 2,暴力统计一下,
(当前时间戳变为 3)结点 5 的类型为 2,点权为 6。发现
这样我们就算出了全部的答案 {10, 3, 10, 5, 6}。
而这种方法的时间复杂度也很低,根据比赛总笔记里的分析,每个点被暴力统计最多不会超过
总结一下,这种路径剖分的方法,妙处主要有:
- 对重边连接的子树信息,可以直接拿来就加,因为它是
size 最大的儿子,暴力肯定不合算。而对于其他相对较轻的儿子,则可以暴力解决。注意,由于结点的信息是依赖于重子结点的,因此计算顺序应该自底向上,这就是为什么要从叶子结点出发。 - 利用时间戳,可以快速判断某些信息是否仍然适用。如果与当前毫无关联(如上例中的 2 与 4),累加就会导致答案错误,但直接全部统一清零一次又会导致太慢(当然,本题直接 memset 似乎可以卡过),这时用时间戳的判断方法就可以根据需要快速清空特定的值。
本题如果不用路径剖分的方法,还可以利用差分思想进行计算,同样是可以 AC 的。
参考代码:
#include <algorithm>#include <cstdio>#include <cstdlib>#include <cstring>#include <iostream>using namespace std;const int MAXN = 1e5 + 100;struct Edge { Edge *next; int dest, typ; //typ = 0 为轻边,1 为重边} edges[MAXN << 1], *current, *first_edge[MAXN], *previous[MAXN];int n, current_time, leaves_cnt;int a[MAXN], b[MAXN], siz[MAXN], leaves[MAXN], num[MAXN], timestamp[MAXN], ans[MAXN];Edge *counterpart(Edge *x) { return edges + ((x - edges) ^ 1);}void insert(int u, int v) { current -> next = first_edge[u]; current -> dest = v; current -> typ = 0; //初始时默认为轻边 first_edge[u] = current ++;}void init_dfs(int root, int pre) { //第一遍预处理的 dfs// printf("%d %d\n", root, pre); siz[root] = 1; Edge *heavy_edge = NULL; //指向重儿子 (如果存在) 的边 for (Edge *p = first_edge[root]; p; p = p -> next) { int v = p -> dest; if (v != pre) { init_dfs(v, root); siz[root] += siz[v]; previous[v] = counterpart(p); //previous 为指向父亲的边 if (!heavy_edge || siz[v] > siz[heavy_edge -> dest]) heavy_edge = p; //取 size 最大的儿子 } } if (siz[root] == 1) leaves[leaves_cnt++] = root; //统计叶子结点 else heavy_edge -> typ = counterpart(heavy_edge) -> typ = 1; //标记重边}void brute_force(int root) { //对轻子树的暴力求解 if (timestamp[b[root]] < current_time) { //如果是过时的信息则要清空 num[b[root]] = 0; timestamp[b[root]] = current_time; //并打上新的时间戳 } num[b[root]] += a[root]; for (Edge *p = first_edge[root]; p; p = p -> next) if (p != previous[root]) brute_force(p -> dest);}void tree_train(int root) { //从叶子结点不断向上的过程 if (timestamp[b[root]] < current_time) { //同理 num[b[root]] = 0; timestamp[b[root]] = current_time; } num[b[root]] += a[root]; for (Edge *p = first_edge[root]; p; p = p -> next) if (p != previous[root] && !p -> typ) brute_force(p -> dest); //对轻子树则暴力求解 //因为是自底向上的,所以重儿子的信息之前已经累加在了 num[] 数组中 ans[root] = num[b[root]]; if (root != 1 && previous[root] -> typ) tree_train(previous[root] -> dest); //注意要判断有父亲才能向上,不然在根结点会爆炸 else return ; //轻边则停止本次}int main(void) { freopen("2104.in", "r", stdin); freopen("2104.out", "w", stdout); scanf("%d", &n); current = edges; fill(first_edge, first_edge + n + 1, (Edge*)0); for (int i = 1; i <= n; i++) scanf("%d%d", &a[i], &b[i]); for (int i = 1; i < n; i++) { int x, y; scanf("%d%d", &x, &y); insert(x, y); insert(y, x); }// puts("build_tree"); init_dfs(1, 0); //puts("init_dfs"); memset(timestamp, -1, sizeof timestamp); for (int i = 0; i < leaves_cnt; i++) {// printf("%d\n", i); current_time = i; //更新当前时间 tree_train(leaves[i]); } for (int i = 1; i <= n; i++) printf("%d ", ans[i]); return 0;}
- [SMOJ2104]树
- 树
- 树
- 树
- 树
- 树
- 树
- 树
- 树
- 树
- 树
- 树
- 树
- 树
- 树
- 树
- 树
- 树
- CVPR 2017论文
- 高效的多维空间点索引算法 — Geohash 和 Google S2(转)
- 网易笔试:小易喜欢的数列
- Exception in thread "main" java.lang.IllegalStateException: Cannot get a text value from a numeric c
- JAVA反射
- [SMOJ2104]树
- mybatis $ # 区别
- Android 布局中如何使控件居中
- NSTimer封装使用
- 写一个简单的颜色选择插件
- BZOJ 3994: [SDOI2015]约数个数和 莫比乌斯反演
- Android中的消息机制
- Maven初始
- 深入浅出: 大小端模式