csu oj 1811: Tree Intersection (启发式合并)

题目链接:http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1811

给你一棵树,每个节点有一个颜色。问删除一条边形成两棵子树,两棵子树有多少种颜色是有相同的。

启发式合并,小的合并到大的中。类似的题目有http://codeforces.com/contest/600/problem/E

 1 //#pragma comment(linker, "/STACK:102400000, 102400000")
 2 #include <algorithm>
 3 #include <iostream>
 4 #include <cstdlib>
 5 #include <cstring>
 6 #include <cstdio>
 7 #include <vector>
 8 #include <cmath>
 9 #include <ctime>
10 #include <list>
11 #include <set>
12 #include <map>
13 using namespace std;
14 typedef long long LL;
15 typedef pair <int, int> P;
16 const int N = 1e5 + 5;
17 struct Edge {
18     int next, to, index;
19 }edge[N << 1];
20 int color[N], head[N], tot;
21 int sum[N], ans[N], res[N]; //sum[color]:颜色color节点个数, ans[u]表示u点及字节点的答案, res[edge]表示边的答案
22 map <int, int> cnt[N]; //cnt[u][color] 表示u点子树color颜色有多少个节点
23
24 void init(int n) {
25     for(int i = 1; i <= n; ++i) {
26         head[i] = -1;
27         sum[i] = 0;
28         cnt[i].clear();
29     }
30     tot = 0;
31 }
32
33 inline void add_edge(int u, int v, int id) {
34     edge[tot].next = head[u];
35     edge[tot].to = v;
36     edge[tot].index = id;
37     head[u] = tot++;
38 }
39
40 void dfs(int u, int pre, int id) {
41     cnt[u][color[u]] = 1;
42     ans[u] = cnt[u][color[u]] < sum[color[u]] ? 1 : 0;
43     for(int i = head[u]; ~i; i = edge[i].next) {
44         int v = edge[i].to;
45         if(v == pre)
46             continue;
47         dfs(v, u, edge[i].index);
48         if(cnt[u].size() < cnt[v].size()) {
49             swap(cnt[u], cnt[v]);
50             swap(ans[u], ans[v]);
51         }
52         for(auto it : cnt[v]) {
53             int &num = cnt[u][it.first];
54             if(num == 0 && num + it.second < sum[it.first]) {
55                 ++ans[u];
56             } else if(num + it.second == sum[it.first] && num) { //说明此子树的it.first颜色节点个数已满
57                 --ans[u];
58             }
59             num += it.second;
60         }
61     }
62     res[id] = ans[u];
63 }
64
65 int main()
66 {
67     int n, u, v;
68     while(scanf("%d", &n) != EOF) {
69         init(n);
70         for(int i = 1; i <= n; ++i) {
71             scanf("%d", color + i);
72             ++sum[color[i]];
73         }
74         for(int i = 1; i < n; ++i) {
75             scanf("%d %d", &u, &v);
76             add_edge(u, v, i);
77             add_edge(v, u, i);
78         }
79         dfs(1, -1, 0);
80         for(int i = 1; i < n; ++i) {
81             printf("%d\n", res[i]);
82         }
83     }
84     return 0;
85 }
时间: 2024-08-29 00:34:57

csu oj 1811: Tree Intersection (启发式合并)的相关文章

CSU 1811 Tree Intersection(平衡树的子树合并)

题意:对于每一条边,去掉一条边后,生成两颗树,问这两颗树的交集大小. 分析:1.O(n^2)算法:每次都用O(n)的时间去合并两个区间,因此可以从合并这个地方去优化.         2.可以这么考虑,在遍历树的时候计算去掉的两树的交集,那么每次我们只要把当前点的所有子树合并便可得到当前点的父边的解.         3.具体实现细节代码见. 复杂度是O(nlog^2n),具体可以参考大白p234那一道题. 吐槽:手挫,思路一早就有了,就代码一直写得-. /*******************

CSU 1811 Tree Intersection

莫队算法,$dfs$序. 题目要求计算将每一条边删除之后分成的两棵树的颜色的交集中元素个数. 例如删除$u->v$,我们只需知道以$v$为$root$的子树中有多少种不同的颜色(记为$qq$),有多少种颜色达到了最多数量(记为$pp$),那么以$v$为$root$的子树与另一棵树的颜色交集中元素个数为$qq-pp$. 因为是计算子树上的量,所以可以将树转换成序列,每一个子树对应了序列中一段区间,具体计算只要用莫队算法分块就可以无脑的计算了. #pragma comment(linker, "

dsu on tree(树上启发式合并)

简介 对于一颗静态树,O(nlogn)时间内处理子树的统计问题.是一种优雅的暴力. 算法思想 很显然,朴素做法下,对于每颗子树对其进行统计的时间复杂度是平方级别的.考虑对树进行一个重链剖分.虽然都基于重链剖分,但不同于树剖,我们维护的不是树链. 对于每个节点,我们先处理其轻儿子所在子树,轻子树在处理完后消除其影响.然后处理重儿子所在子树,保留其贡献.然后再暴力跑该点的轻子树,统计该点子树的最终答案.如果该点子树是轻子树,则消除该子树的影响,否则保留.用代码描述的话,大概是这个流程: void d

“优美的暴力”——树上启发式合并

今天介绍一个神仙算法:Dsu On Tree[ 树上启发式合并 ] 这个算法用于离线处理询问子树信息,而且很好写. 但是在你没有理解它之前,这是个很鬼畜的算法. 理解后你才能真心感到它的美妙之处. 关键是它是有着媲美线段树合并的时间复杂度的“暴力”算法. 这里说一件事,我学这个东西时找了很多篇博客,它们无一例外地给出了这样一个流程: 1. 先统计一个节点所有的轻儿子 然后删除它的答案2. 再统计这个节点的重儿子 保留他的答案3. 再算一遍所有轻儿子 加到答案中上传 我当时就看的很懵逼,算一遍所有

图论-树上启发式合并(DSU On Tree)

Disjoint Set Union On Tree ,似乎是来自 Codeforces 的一种新操作,似乎被叫做"树上启发式合并". 在不带修改的有根树子树信息统计问题中,似乎树上莫队和这个 DSU On Tree 是两类常规操作. 先对树按轻重链剖分.对于每个节点,先计算轻儿子为根的子树信息,每次计算后消除影响,再去计算其他轻儿子.然后计算重儿子为根的子树信息,不消除影响,并把轻儿子们为根的子树信息加入,再合并这个节点本身的信息.由于一个大小 \(x\) 的子树被消除影响后,都把信

2016湖南省赛 I Tree Intersection(线段树合并,树链剖分)

2016湖南省赛 I Tree Intersection(线段树合并,树链剖分) 传送门:https://ac.nowcoder.com/acm/contest/1112/I 题意: 给你一个n个结点的树,树上每个节点有自己的颜色 问你删除第i条边后形成的两颗子树有多少个相同的颜色 题解: 树链剖分写法: 对于每一种颜色来说,如果这个颜色是在单独的一颗子树中,那么就不会对其他的边产生贡献,所以我们单独对每一种颜色对边的贡献讨论,如果这个颜色只有一个,那么就不会产生贡献,否则,他就可以在两个相同颜

codeforces 375D . Tree and Queries 启发式合并 || dfs序+莫队

题目链接 一个n个节点的树, 每一个节点有一个颜色, 1是根节点. m个询问, 每个询问给出u, k. 输出u的子树中出现次数大于等于k的颜色的数量. 启发式合并, 先将输入读进来, 然后dfs完一个节点就处理跟它有关的询问. 感觉不是很难, 然而.....WA了n次最后还是看的别人的代码 1 #include <iostream> 2 #include <vector> 3 #include <cstdio> 4 #include <cstring> 5

HDU6191 Query on A Tree (01字典树+启发式合并)

题意: 给你一棵1e5的有根树,每个节点有点权,1e5个询问(u,x),问你子树u中与x异或最大的值是多少 思路: 自下而上启发式合并01字典树,注意合并时清空trie 线段树.字典树这种结构确定的数据结构,启发式合并的时候不需要考虑次序,复杂度都是nlogn 代码: 2200 / 10000ms , 60 / 128 M #include<iostream> #include<cstdio> #include<algorithm> #include<cmath&

CodeForces 375D. Tree and Queries【树上启发式合并】

传送门 题意 给出一棵 \(n\) 个结点的树,每个结点有一个颜色 \(c_i\) . 询问 \(q\) 次,每次询问以 \(v\) 结点为根的子树中,出现次数 \(\ge k\) 的颜色有多少种.树的根节点是 \(1\). 题解 反正我看见这个 \(\ge k\) 就觉得要用线段树,实际上好像不用写线段树的 Orz. 还是树上启发式合并,记录每种颜色出现的次数,然后线段树记录某种次数有多少颜色,更改就在线段树上改. 这是最后一道树上启发式合并的例题了,以后遇到再刷. #include <bit