【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)
(这题在洛谷主站居然搜不到……还是在百度上偶然看到的)
题目描述
给一棵根为1的树,每次询问子树颜色种类数
输入输出格式
输入格式:
第一行一个整数n,表示树的结点数
接下来n-1行,每行一条边
接下来一行n个数,表示每个结点的颜色c[i]
接下来一个数m,表示询问数
接下来m行表示询问的子树
输出格式:
对于每个询问,输出该子树颜色数
说明
对于前三组数据,1<=m,c[i]<=n<=100
1<=m,c[i]<=n<=1e5
本来在学树上启发式合并,偶然看到这个题,就当模板来打了。
树上启发式合并(dsu on tree)是一种对静态子树节点信息的统计手段。和树上点分治一样,二者都是经过优化的暴力做法,启发式合并的复杂度可以从平方级别降到O(nlogn)。
启发式合并的思想源自暴力统计子树信息的过程。这题中,某棵树的每个点都有一个颜色,我们要多组询问统计某个点为根的子树内有多少种颜色。
这不是主席树乱搞嘛
考虑直接做这个题的话,我们想预处理出每个点的答案,可以直接从每个点出发做一次dfs统计得出答案后再dfs一遍,把它清空。这个显然的做法是O(n^2)的。
dsu on tree给出的优化策略是,我们想要统计某个点u,可以先遍历完它的轻儿子们,把这些点清空消去对重儿子的影响,后再去统计它的重儿子。这样得到的重儿子的信息我们还可以利用在u上,所以这时候我们保留重儿子的信息,再暴力地把它的轻儿子统计一遍来更新u的答案就可以了。
直观上看,每次特殊对待(只统计一次)的这个节点选择重儿子显然是最优的。启发式合并的复杂度经过证明可以达到O(nlogn);考虑每个点,这个点会被统计多少次呢?
1、通过轻边被扫到。根据轻重链的性质,这一步最多会有log次。
2、被遍历被扫到一次,或者藉由所在重链被扫到一次。是的,我们在合并的过程中遍历重链就像树剖的第二次dfs一样,每条重链只会被扫一次,因此这两种情况是O(1)的。
dsu on tree的复杂度得到了证明,我们可以愉快地用它来套做题了。需要强调的是,这个算法适用的是“静态统计子树信息”的问题。具体的遍历写法见代码:
- #include <iostream>
- #include <cstdio>
- #include <cstring>
- #include <algorithm>
- #include <cctype>
- #define maxn 100010
- using namespace std;
- template <class T>
- void read(T &x) {
- x = 0;
- char ch = getchar();
- while (!isdigit(ch))
- ch = getchar();
- while (isdigit(ch)) {
- x = x * 10 + (ch ^ 48);
- ch = getchar();
- }
- }
- int head[maxn], top = 1;
- struct E {
- int to, nxt;
- } edge[maxn << 1];
- inline void insert(int u, int v) {
- edge[++top] = (E) {v, head[u]};
- head[u] = top;
- }
- int n, m;
- int cnt[maxn], color[maxn], son[maxn], size[maxn], ans[maxn], NowSon, sum;
- void dfs1(int u, int pre) { //预处理子树大小
- size[u] = 1;
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (v == pre) continue;
- dfs1(v, u);
- size[u] += size[v];
- if (size[son[u]] < size[v])
- son[u] = v;
- }
- }
- void cal(int u, int pre, int val) { //计算答案
- if (!cnt[color[u]]) ++sum;//在当前树中统计这种颜色
- cnt[color[u]] += val;
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (v == pre || v == NowSon)//避开根的重儿子
- continue;
- cal(v, u, val);
- }
- }
- void dsu(int u, int pre, bool op) {//启发式合并的主体。op为 0表示这次操作由轻边遍历得到,需要清空
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (v == pre || v == son[u])
- continue;
- dsu(v, u, 0);//轻儿子
- }
- if (son[u])
- dsu(son[u], u, 1), NowSon = son[u];//重儿子
- cal(u, pre, 1); NowSon = 0;//重新扫描轻儿子,二次统计
- ans[u] = sum;//那么现在的颜色数就是u的信息
- if (!op) {//清空当前的统计数
- cal(u, pre, -1);
- sum = 0;
- }
- }
- int main() {
- read(n);
- int u, v;
- for (int i = 1; i < n; ++i) {
- read(u), read(v);
- insert(u, v), insert(v, u);
- }
- for (int i = 1; i <= n; ++i)
- read(color[i]);
- dfs1(1, 0);
- dsu(1, 0, 1);
- read(m);
- for (int i = 1; i <= m; ++i) {
- read(v);
- printf("%d\n", ans[v]);
- }
- return 0;
- }
【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)的更多相关文章
- 神奇的树上启发式合并 (dsu on tree)
参考资料 https://www.cnblogs.com/zhoushuyu/p/9069164.html https://www.cnblogs.com/candy99/p/dsuontree.ht ...
- 树上启发式合并 (dsu on tree)
这个故事告诉我们,在做一个辣鸡出题人的比赛之前,最好先看看他发明了什么新姿势= =居然直接出了道裸题 参考链接: http://codeforces.com/blog/entry/44351(原文) ...
- CF600E Lomsat gelral——线段树合并/dsu on tree
题目描述 一棵树有$n$个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和. 这个题意是真的窒息...具体意思是说,每个节点有一个颜色,你要找的是每个子树中颜色的众数 ...
- bzoj3307雨天的尾巴(权值线段树合并/DSU on tree)
题目大意: 一颗树,想要在树链上添加同一物品,问最后每个点上哪个物品最多. 解题思路: 1.线段树合并 假如说物品数量少到可以暴力添加,且树点极少,我们怎么做. 首先在一个树节点上标记出哪些物品有多少 ...
- 【LUOGU???】WD与数列 sam 启发式合并
题目大意 给你一个字符串,求有多少对不相交且相同的子串. 位置不同算多对. \(n\leq 300000\) 题解 先把后缀树建出来. DFS 整棵树,维护当前子树的 right 集合. 合并两个集合 ...
- 【Luogu】P1903数颜色(带修改莫队)
题目链接 带修改莫队模板. 加一个变量记录现在是第几次修改,看看当前枚举的询问是第几次修改,改少了就改过去,改多了就改回来. 话说我栈用成队列了能过样例?!!!! 从此深信一句话:样例是出题人精心设计 ...
- 【CF600E】Lomset gelral 题解(树上启发式合并)
题目链接 题目大意:给出一颗含有$n$个结点的树,每个节点有一个颜色.求树中每个子树最多的颜色的编号和. ------------------------- 树上启发式合并(dsu on tree). ...
- 牛客练习赛47 E DongDong数颜色 (树上启发式合并)
链接:https://ac.nowcoder.com/acm/contest/904/E 来源:牛客网 DongDong数颜色 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 5242 ...
- luoguP3302 [SDOI2013]森林 主席树 启发式合并
题目链接 luoguP3302 [SDOI2013]森林 题解 本来这题树上主席树暴力启发式合并就完了 结果把lca写错了... 以后再也不这么写了 复杂度\(O(nlog^2n)\) "f ...
随机推荐
- JUC---04Lock(二)ReentrantReadWriteLock
1.读写锁 分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可.如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁:如果你的代码修改数据,只能 ...
- 晚间测试13 A. Dove 打扑克 vector +模拟
题目描述 分析 这道题比较关键的一点就是要看出最终牌数的种类数不会超过 \(\sqrt{n}\) 种 知道了这个性质我们就可以用 \(vector\) 维护一个有序的序列 \(vector\) 中存放 ...
- Java 运行时动态生成class
转载 http://www.liaoxuefeng.com/article/0014617596492474eea2227bf04477e83e6d094683e0536000 Java是一门静态语言 ...
- NB-IOT关键技术分析
NB-IOT(NarrowBand Internet of Things,窄带IoT)是一种基于蜂窝的窄带物联网技术,支持低功耗设备在广域网的蜂窝数据连接.NB-IOT在物联网应用广泛,许多领域都充分 ...
- Java学习的第十九天
1.今天学了接口只能有抽象的常量和方法,接口为interface 承接接口是implements 接口的使用 接口中的方法必须是抽象的,没有构造方法 2.今天没有问题 3.明天学习第六章综合实例 ...
- 04 . Go+Vue开发一个线上外卖应用(用户名密码和图形验证码)
图形化验证码生成和验证 功能介绍 在使用用户名和密码登录功能时,需要填写验证码,验证码是以图形化的方式进行获取和展示的. 验证码使用原理 验证码的使用流程和原理为:在服务器端负责生成图形化验证码,并以 ...
- 快快使用ModelArts,零基础小白也能玩转AI!
摘要: 走过路过不要错过,看Copy攻城狮如何借力华为云ModelArts玩转AI. "自2018年10月发布以来,ModelArts累计服务了众多行业十几万开发者,通过基础平台的完备性和面 ...
- rabbitmq(一)-基础入门
原文地址:https://www.jianshu.com/p/e186a7fce8cc 在学东西之前,我们先有一个方法论,知道如何学习.学习一个东西一般都遵循以下几个环节: xxx是什么,诞生的原因, ...
- pytorch训练GAN时的detach()
我最近在学使用Pytorch写GAN代码,发现有些代码在训练部分细节有略微不同,其中有的人用到了detach()函数截断梯度流,有的人没用detch(),取而代之的是在损失函数在反向传播过程中将bac ...
- EF6 Code First 博客学习记录
学习一下ef6的用法 这个学习过程时按照微软官网的流程模拟了一下 就按照下面的顺序来写吧 1.连接数据库 自动生成数据库 2.数据库迁移 3.地理位置以及同步/异步处理(空了再补) 4.完全自动迁移 ...