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

传送门

题意

给出一棵 \(n\) 个结点的树,每个结点有一个颜色 \(c_i\) 。 询问 \(q\) 次,每次询问以 \(v\) 结点为根的子树中,出现次数 \(\ge k\) 的颜色有多少种。树的根节点是 \(1\)。

题解

反正我看见这个 \(\ge k\) 就觉得要用线段树,实际上好像不用写线段树的 Orz。

还是树上启发式合并,记录每种颜色出现的次数,然后线段树记录某种次数有多少颜色,更改就在线段树上改。

这是最后一道树上启发式合并的例题了,以后遇到再刷。

#include <bits/stdc++.h>
#define xx first
#define yy second
using namespace std;
typedef pair<int,int> PII;
const int N=1e5+10;
int n,m,clr[N],siz[N],son[N],cnt[N],ans[N],Son;
vector<PII> ask[N];
vector<int> g[N];
struct SegTree{
	#define mid (l+r>>1)
	int sum[N*4];
	void upd(int id,int l,int r,int pos,int x){
		if(l==r) {sum[id]+=x;return;}
		if(pos<=mid) upd(id<<1,l,mid,pos,x);
		else upd(id<<1|1,mid+1,r,pos,x);
		sum[id]=sum[id<<1]+sum[id<<1|1];
	}
	int ask(int id,int l,int r,int L,int R){
		if(L<=l&&r<=R) return sum[id];
		int res=0;
		if(L<=mid) res+=ask(id<<1,l,mid,L,R);
		if(R>mid) res+=ask(id<<1|1,mid+1,r,L,R);
		return res;
	}
	#undef mid
}tr;

void predfs(int u,int fa){
	siz[u]=1;
	for(int v:g[u]){
		if(v==fa) continue;
		predfs(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
}

void add(int u,int fa,int val){
	tr.upd(1,0,1e5,cnt[clr[u]],-1);
	cnt[clr[u]]+=val;
	tr.upd(1,0,1e5,cnt[clr[u]],1);
	for(int v:g[u]) if(v!=fa&&v!=Son) add(v,u,val);
}

void dfs(int u,int fa,bool keep){
	for(int v:g[u]){
		if(v==fa||v==son[u]) continue;
		dfs(v,u,false);
	}
	if(son[u]) dfs(son[u],u,true),Son=son[u];
	add(u,fa,1);
	for(PII q:ask[u]) ans[q.xx]=tr.ask(1,0,1e5,q.yy,1e5);
	if(!keep) Son=0,add(u,fa,-1);
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&clr[i]);
	for(int i=1,u,v;i<n;i++){
		scanf("%d%d",&u,&v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	for(int i=1,u,k;i<=m;i++){
		scanf("%d%d",&u,&k);
		ask[u].push_back({i,k});
	}
	predfs(1,0);
	tr.upd(1,0,1e5,0,1e5);
	dfs(1,0,true);
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
}

原文地址:https://www.cnblogs.com/BakaCirno/p/12612303.html

时间: 2024-10-12 08:46:53

CodeForces 375D. Tree and Queries【树上启发式合并】的相关文章

CodeForces 600E. Lomsat gelral【树上启发式合并】

传送门 好像大家都是拿这道题作为树上启发式合并的板子题. 树上启发式合并,英文是 dsu on tree,感觉还是中文的说法更准确,因为这个算法和并查集(dsu)没有任何关系.一般用来求解有根树的所有子树的统计问题. 根据轻重儿子的各种性质,可以证明这个算法的时间复杂度为 \(O(nlogn)\),虽然看起来暴力的不行,但是却是一个很高效的算法. 算法的核心其实就是对于每个节点,先计算轻儿子,再计算重儿子,把自己和轻儿子的所有贡献累计到重儿子上去,如果自己是轻儿子,就把贡献清空. 如果掌握了树链

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

Codeforces 375D Tree and Queries(DFS序+莫队+树状数组)

题目链接  Tree and Queries 题目大意  给出一棵树和每个节点的颜色.每次询问vj, kj 你需要回答在以vj为根的子树中满足条件的的颜色数目, 条件:具有该颜色的节点数量至少为kj. (莫队居然可以过) 首先转DFS序,这样就变成了区间查询. 然后直接套用莫队,求出每次询问状态下的t[],t[k]表示当前区间内拥有k个节点的颜色数量. 然后统计t[k] + t[k + 1], ..., t[MAX]即可,这个过程用树状数组维护. #include <bits/stdc++.h>

CodeForces 375D Tree and Queries 莫队||DFS序

Tree and Queries 题意:有一颗以1号节点为根的树,每一个节点有一个自己的颜色,求出节点v的子数上颜色出现次数>=k的颜色种类. 题解:使用莫队处理这个问题,将树转变成DFS序区间,然后就是开一个数据记录下出现次数为k次的颜色数目,查询的时候直接返回这个数组中的对应的值就行了. 注意的就是记得将节点的颜色传递给dfs序对应位置的颜色. 这个忘记了找了好久的bug. 代码: 1 #include<bits/stdc++.h> 2 using namespace std; 3

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

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

【基本操作】树上启发式合并の详解

树上启发式合并是某些神仙题目的常见操作. 这里有一个讲得详细一点的,不过为了深刻记忆,我还是再给自己讲一遍吧! DSU(Disjoint Set Union),别看英文名挺高级,其实它就是并查集…… DSU on tree,也就是树上的启发式合并(众所周知,并查集最重要的优化就是启发式合并). 然后咱们来考虑一个基础题:给出一棵树,每个节点有颜色,询问一些子树中不同的颜色数量(颜色可重复).祖传数据($100000$). 当然,这道题可以被各种方法切,比如带修莫队(做法自行百度). 但莫队的时空

树上启发式合并入门

前言 树上启发式合并,即\(DSU\ on\ Tree\),是一个挺好用.挺实用的树上信息维护方法. 由于它比较简单,容易理解,因此这里也就简单记录一下吧. 前置知识:重儿子 什么是重儿子? 这应该是树链剖分中的一个概念吧.重儿子就是某个节点的子节点中,子树大小最大的节点. 适用情况 你可以很方便地给每个点染上白色和黑色,且你需要对于每个点都分别得到其子树内节点为黑.子树外节点为白的局面. 具体实现 这是一个比较贪心的过程. 考虑\(dfs\)遍历时,对于当前点的每个儿子,除最后操作的儿子以外,

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

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

【CF600E】Lomsat gelral——树上启发式合并

(题面来自luogu) 题意翻译 一棵树有n个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和. ci <= n <= 1e5 树上启发式合并裸题.统计时先扫一遍得到出现次数最大值,然后再扫一遍看哪个颜色的出现次数与mxCnt相等.注意用一个bool数组判重,清空轻儿子贡献时要顺手把bool数组也打成false. 代码: #include <iostream> #include <cstdio> #include <cctype&