欢迎神犇吊|

Hanx16Msgr

园龄:2年8个月粉丝:12关注:3

2022-08-17 10:30阅读: 31评论: 0推荐: 0

CF375D Tree and Queries / Dsu on tree 模板

Tree and Queries

CF375D (Luogu)

题面翻译

  • 给定一棵 n 个节点的树,根节点为 1。每个节点上有一个颜色 cim 次操作。操作有一种:
    1. u k:询问在以 u 为根的子树中,出现次数 k 的颜色有多少种。
  • 2n1051m1051ci,k105

题目描述

You have a rooted tree consisting of n vertices. Each vertex of the tree has some color. We will assume that the tree vertices are numbered by integers from 1 to n . Then we represent the color of vertex v as cv . The tree root is a vertex with number 1.

In this problem you need to answer to m queries. Each query is described by two integers vj,kj . The answer to query vj,kj is the number of such colors of vertices x , that the subtree of vertex vj contains at least kj vertices of color x .

You can find the definition of a rooted tree by the following link: http://en.wikipedia.org/wiki/Tree_(graph_theory).

输入格式

The first line contains two integers n and m (2<=n<=105;1<=m<=105) . The next line contains a sequence of integers c1,c2,...,cn (1<=ci<=105) . The next n1 lines contain the edges of the tree. The i -th line contains the numbers ai,bi (1<=ai,bi<=n;aibi) — the vertices connected by an edge of the tree.

Next m lines contain the queries. The j -th line contains two integers vj,kj (1<=vj<=n;1<=kj<=105) .

输出格式

Print m integers — the answers to the queries in the order the queries appear in the input.

样例 #1

样例输入 #1

8 5
1 2 2 3 3 2 3 3
1 2
1 5
2 3
2 4
5 6
5 7
5 8
1 2
1 3
1 4
2 3
5 3

样例输出 #1

2
2
1
0
1

样例 #2

样例输入 #2

4 1
1 2 3 4
1 2
2 3
3 4
1 1

样例输出 #2

4

提示

A subtree of vertex v in a rooted tree with root r is a set of vertices u:dist(r,v)+dist(v,u)=dist(r,u) . Where dist(x,y) is the length (in edges) of the shortest path between vertices x and y .

Solution

对于刚学 Dsu on tree,这道题可以作为模板题来进行入门以及基础思想的熟悉和练习。

Dsu on tree / 树上启发式合并,是一种根据经验来对树上的合并操作进行优化的办法,一般适用于需要统计子树的颜色种类,并且是离线的。这种做法既不像树套树一样难写,也不像树上莫队一样有 O(nn) 的时间复杂度。Dsu on tree 的代码不难写,并且时间复杂度是 O(nlogn) 的。因此在一些题中,用 Dsu on tree 可以糊一些部分分,甚至于暴打 std

将每个点的颜色存储在 col 数组中,用 cnt 作为桶来统计颜色,用 sumi 来表示颜色数量至少为 i 的颜色数。首先先像重轻链剖分一样用一个 DFS 跑出每个点的重儿子、子树大小、DFS 序。然后对于每一个子树进行以下操作:

  • 计算所有轻儿子的答案并清除对 cntsum 数组的贡献。
  • 计算重儿子答案并且保留对 cntsum 的贡献。
  • 将轻儿子贡献加入 cntsum 数组并更新答案

用到这种启发式合并思想的还有按秩合并并查集(将小的集合并入大的集合,从而达到 O(logn) 的时间复杂度)。Dsu on tree 的时间复杂度证明比较麻烦,不过可以感性认识:因为树上的重儿子个数最多只有 logn 个,所以时间复杂度就是 O(nlogn) 的。

可能参考代码更好理解:

#include<bits/stdc++.h>
using namespace std;
template<typename T> void read(T &k)
{
	k=0;T flag=1;char b=getchar();
	while (!isdigit(b)) {flag=(b=='-')?-1:1;b=getchar();}
	while (isdigit(b)) {k=k*10+b-48;b=getchar();}
	k*=flag;
}
template<typename T> void write(T k) {if (k<0) putchar('-'),write(-k);if (k>9) write(k/10);putchar(k%10+48);}
template<typename T> void writewith(T k,char c) {write(k);putchar(c);}
const int _SIZE=1e5;
struct EDGE{
	int nxt,to;
}edge[(_SIZE<<1)+5];
int tot,head[_SIZE+5];
void AddEdge(int x,int y)
{
	edge[++tot]=(EDGE){head[x],y};
	head[x]=tot;
}
int n,m,col[_SIZE+5];
int id[_SIZE+5],node[_SIZE+5],son[_SIZE+5],siz[_SIZE+5],dfn;
int ans[_SIZE+5];
int cnt[_SIZE+5],sum[_SIZE+5];
vector<pair<int,int> > vec[_SIZE+5];//用于存储对于每个节点的询问
void dfs1(int x,int fa)//第一遍dfs,跑dfs序,重儿子和size
{
	id[x]=++dfn;//dfs序
	node[dfn]=x;//dfs序对应的节点
	siz[x]=1;
	int maxson=-1;
	for (int i=head[x];i;i=edge[i].nxt)
	{
		int twd=edge[i].to;
		if (twd==fa) continue;
		dfs1(twd,x);
		siz[x]+=siz[twd];
		if (siz[twd]>maxson) maxson=siz[twd],son[x]=twd;
	}
}
void add(int x) {cnt[col[x]]++;sum[cnt[col[x]]]++;}//加入该点的影响
void del(int x) {sum[cnt[col[x]]]--;cnt[col[x]]--;}//清除该点的影响
void dfs2(int x,int fa,bool keep)//keep用于告知当前点对cnt和sum的贡献是否应该清除
{
	for (int i=head[x];i;i=edge[i].nxt)//计算轻儿子答案
	{
		int twd=edge[i].to;
		if (twd==fa || twd==son[x]) continue;
		dfs2(twd,x,false);//不保留贡献
	}
	if (son[x]) dfs2(son[x],x,true);add(x);//计算重儿子答案,保留贡献,加入当前点的贡献
	for (int i=head[x];i;i=edge[i].nxt)//加入轻儿子的贡献
	{
		int twd=edge[i].to;
		if (twd==fa || twd==son[x]) continue;
		for (int j=id[twd];j<=id[twd]+siz[twd]-1;j++) add(node[j]);//同一子树dfs序连续,直接for即可
	}
	for (auto i:vec[x]) ans[i.first]=sum[i.second];//遍历当前节点x的询问并更新答案
	if (!keep) for (int i=id[x];i<=id[x]+siz[x]-1;i++) del(node[i]);//如果需要不保留贡献则将当前节点的子树全部清除贡献
}
int main()
{
	read(n);read(m);
	for (int i=1;i<=n;i++) read(col[i]);
	for (int i=1;i<n;i++) 
	{
		int u,v;read(u),read(v);
		AddEdge(u,v);AddEdge(v,u);
	}
	for (int i=1;i<=m;i++)
	{
		int u,k;read(u),read(k);
		vec[u].push_back(make_pair(i,k));//存储id和k到对应的u节点上
	}
	dfs1(1,0);
	dfs2(1,0,1);
	for (int i=1;i<=m;i++) writewith(ans[i],'\n');puts("");
	return 0;
}

posted @   Hanx16Msgr  阅读(31)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起