树上启发式合并问题 ---- D. Tree and Queries[树上启发式合并+树状数组]

题目链接


题目大意:

就是给你一棵树,树上每个节点都有一个颜色,在你 m m m次询问每次询问给你一个节点 u u u和一个数字 k k k,问你在 u u u这颗子树里面又少种颜色的结点个数是大于 k k k;


解题思路:

看到子树问题我们最先考虑就是树上启发式合并,或者是dfs序化成区间问题,那么就是一个莫队问题了。听说这道题数据水莫队可以冲过去

我们现在来讲一下树上启发式合并的解法:
树上启发式合并的关键点就是在于如何把轻儿子和重儿子的答案进行合并?
1.其实对于查询有多少个数字大过 K K K其实最好的办法无疑就是树状数组,我们记录一下插入了多少个数 S u m Sum Sum,然后减去小于 K K K的数的个数。
2.对于清空答案我们要把出现过的数用东西记录下来,不能每次都暴力清空,因为这样会使得复杂度退化。


#include <bits/stdc++.h>

using namespace std;
const int maxn = 2e5 + 10;
int n, m;
int col[maxn], ans[maxn];//col记录每个点的颜色
vector<int> G[maxn];
vector<pair<int,int>> ask[maxn];
//................................
int tr[maxn];
inline int lowbit(int x) {
	return x & (-x);
}

inline void add(int x, int val) {
	while(x < maxn) {
        tr[x] += val;
		x += lowbit(x);
	}
}

inline int sum(int x) {
	int res = 0;
	while(x) {
		res += tr[x];
		x -= lowbit(x);
	}
	return res;
}

//................................
int siz[maxn], son[maxn];
void find_son(int u, int fa) {
	siz[u] = 1;
	son[u] = 0;
	for(auto it : G[u]) {
		if(it == fa) continue;
		find_son(it,u);
        siz[u] = siz[u] + siz[it];
		if(siz[son[u]] < siz[it]) 
		  son[u] = it;
	}
}

int flag;
int cnt[maxn], Sum;//cnt记录每个颜色出现的次数,Sum记录有多少个点插入了树状数组
unordered_map<int,int> po;
int num[maxn], idx;

void Count(int u, int fa) {
	num[idx++] = col[u];

	if(po.count(cnt[col[u]])) { 
		if(--po[cnt[col[u]]] == 0) po.erase(cnt[col[u]]);
	}
	
	if(cnt[col[u]]) add(cnt[col[u]],-1), Sum --;
    add(++cnt[col[u]],1), Sum ++;

	if(po.count(cnt[col[u]])) po[cnt[col[u]]] ++;
	else po[cnt[col[u]]] = 1;
	
	for(auto it : G[u]) {
		if(it == fa || it == flag) continue;
		Count(it,u);
	}
}

void dsu(int u, int fa, int keep) {
    
	for(auto it : G[u]) {
		if(it == fa || it == son[u]) continue;
		dsu(it,u,0);
	}
    
	if(son[u]) {
       dsu(son[u],u,1);
       flag = son[u];
	}
    
    Count(u,fa);
	
	for(auto it : ask[u]) 
        ans[it.second] = Sum - sum(it.first-1);
	flag = 0;
	
	if(!keep) {
		for(auto it : po) add(it.first,-it.second);//清空树状数组
		for(int i = 0; i < idx; ++ i) cnt[num[i]] = 0;//清空记录颜色个数的数组
		idx = 0; Sum = 0; po.clear();
	}
}

int main() {
    ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	//...................
	cin >> n >> m;
	for(int i = 1; i <= n; ++ i)
	   cin >> col[i];
	for(int i = 1; i < n; ++ i) {
		int u, v;
		cin >> u >> v;
        G[u].push_back(v);
		G[v].push_back(u);
	}
    for(int i = 0; i < m; i ++) {
		int u, k;
		cin >> u >> k;
		ask[u].push_back({k,i});//记录询问
	}
    find_son(1,0);
    dsu(1,0,1);
	for(int i = 0; i < m; ++ i)
	   cout << ans[i] << "\n";
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值