2021牛客多校9E Eyjafjalla (主席树+倍增法)

调了很久的树套树,最后华丽丽的tle了
题目大意:
给定一个树形结构,1号点是树根.越接近树根温度越高,现有一种生存温度在[L,R]区间病毒在点X处出现,如果某个节点的温度在[L,R]区间,且与其相邻的点感染了病毒,那么这个点也会感染病毒
有很多询问,每次询问在X点爆发一种生存温度[L,R]的病毒,最多能感染多少个点
先想想暴力做法:暴力dfs,必然会TLE
考虑到题目中给出的是一个树形结构,且温度的增长是有单调性的,病毒传染的过程就可以分成两步:先从当前点出发,找到温度小于等于R且最接近源点的点(找祖先),随后从该点向下传染,感染子树中所有温度区间在[L,R]的点
第一步找祖先可以用倍增的方法找

=======begin
(知道倍增法的可以跳过这部分)
倍增的原理:
如果跳远比赛要刚好跳到60米处,可以多次跳跃,而你只能固定跳跃以下距离:1,2,4,8,16 32,64,128米
先尝试用128米跳,显然不行,会超出60米现在,就换用64米跳,也不行,于是用32米,可行,此时你的位置在32米处,距离60米还有28米,接下来用16米跳,可行,还有12米,接下来用8米跳,可行,还有4米,再用4米跳一次就能正好跳到60米处
这就是倍增的原理
如何构造倍增数组:
设一个数组int up[N][30] ,up[i][j]代表从i号点,向上遍历2^j个节点后到达的点的编号
其中up[i][0]代表i点向上移动2^0=1个点所到达的点的编号,也就是它的父节点
假设j点是i点的父节点,且up[j][0]=k,即k是y的父节点
那么k就是i的祖父节点
up[i][1]代表的是i向上移动2^1个节点到达的位置,也就是它的祖父节点K
同时的,i点向上移动2个点到达K等同于其父节点向上移动一个点到达K
也就是up[i][1]=up[父节点][0]
也就是是说up[i][j] = up[up[i][j - 1]][j - 1];
这种思想类似于DP,完整的推导代码为:

for (int j = 1; j <= 20; j++) {
    
     
		for (int i = 1; i <= n; i++) {
    
    
			up[i][j] = up[up[i][j - 1]][j - 1];
		}
	}

这样就能快速构建出i点跳跃2^j个点后所到达的点
放在本题中:如何快速找到X点的主线中温度最高且最接近源点的点
我们就从远到近进行尝试跳跃,令i=20,第一次跳跃如果
:温度[up[x][i]]>r,那么说明这一步跳远了,就让i-1,再继续尝试跳跃
当直到某一次温度[up[x][i]] <= r 说明是可以跳过去的,就让x = up[x][i];
但跳一次并不算完,在i-1后继续尝试跳跃,直到i<0为止
代码:

for (int i = 20; i >= 0; i--) {
    
     
			if (t[up[x][i]] <= r)
				x = up[x][i];
		}

===========end
接下来就是看这个祖先节点的子树下有多少个点的 R>=温度>=L

============begin

(知道主席树是什么的可以跳过这部分)
主席树==可持久化线段树
首先,这是一个可爱的普通线段树
请添加图片描述
当我们对其中一个点进行修改,但又想保留修改前的信息,如果暴力方法就是再新建一个线段树,除了修改的点之外,其他点的信息一一拷贝
(黑色的点就是被修改的点)
请添加图片描述
但这样十分消耗空间,所以我们就考虑仅新建会被修改的点
请添加图片描述
这些新建节点未被修改的子树就指向原本的这棵线段树
请添加图片描述
这样,只要记录好每个新树的树根的编号在root[N],就能获取各个版本的线段树了

=========end

===================begin

(知道dfs序建主席树的可以跳过这个部分)
首先,想要知道这个祖先节点的子树中有哪些点,可以用dfs遍历整个树,记录下递归进入(in)和退出(out)的时间戳,就可以知道有哪些点

void dfs(int u, int f) {
    
    
	fa[u] = f;
	in[u] = ++idx;
	ord[idx] = u;
	for (int i : g[u]) {
    
    
		if (i != f)
			dfs(i, u);
	}
	out[u] = idx;
}

在递归完成后,各个点的in和out
请添加图片描述

(数对中的第一个数是in,第二个数是out)
例如找2号点的子节点,其in为2,out为3,在1 2 3 4中覆盖了下标为2和3的区域,这个区域内的编号就是其子节点的编号

========================end

我们可以维护一个主席树,节点维护各个版本中温度L,到R之间的节点数量
但是在问询时出现了两个区间,一个是当前子树区间,一个是温度区间[L,R]
所以可以用类前缀和的思想,以dfs序建主席树,随后分别问询root[out[x]]版本(已经将x点的子树添加完毕的版本)下温度区间在[L,R]中的点的数量,再问询root[in[x]-1]版本(还未开始添加x点及其子树的版本)中温度区间在[L,R]中的点的数量.两者相减,就是x子树下温度区间在[L,R]中的点
完整代码

#include <bits/stdc++.h>
using namespace std;
const int N = 100010; //点数
vector<int>g[N];
int t[N];//温度
int fa[N];
int up[N][30];//倍增数组
int in[N], out[N]; //记录出入时的dfs序
int ord[N];//记录dfs序中in数组对应的点
int idx;

void dfs(int u, int f) {
    
    
	fa[u] = f;
	in[u] = ++idx;
	ord[idx] = u;
	for (int i : g[u]) {
    
    
		if (i != f)
			dfs(i, u);
	}
	out[u] = idx;
}

struct node {
    
     //主席树节点
	int val;//维护节点中
	int l, r; //左右儿子的编号
} a[20001000];

int root[N];//记录版本
int tot;//版本数量
//           新版本 旧版本   温度区域 执行插入的温度位置
void modify(int x, int f, int l, int r, int pos) {
    
    
	if (l == r) {
    
    
		a[x].val = a[f].val + 1;
		return ;
	}
	int mid = l + r >> 1;
	if (pos <= mid) {
    
    
		a[x].r = a[f].r; //修改区间在左边,不会影响上个版本的右儿子
		a[x].l = ++tot; //赋新点
		modify(a[x].l, a[f].l, l, mid, pos);
	} else {
    
    
		a[x].l = a[f].l;
		a[x].r = ++tot;
		modify(a[x].r, a[f].r, mid + 1, r, pos);
	}
	a[x].val = a[a[x].l].val + a[a[x].r].val;

}
//                   当前区间    问询区间
int query(int x, int l, int r, int ll, int rr) {
    
    
	if (x == 0)
		return 0;
	if (l >= ll && r <= rr)
		return a[x].val;
	if (l > rr || r < ll)
		return 0;
	int mid = l + r >> 1;
	return query(a[x].l, l, mid, ll, rr) + query(a[x].r, mid + 1, r, ll, rr);
}


int main() {
    
    
	ios::sync_with_stdio(false);
	int n;
	cin >> n;
	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 = 1; i <= n; i++)
		cin >> t[i];
	t[0] = 1e9+10;
	dfs(1, 0); 
	
	
	for (int i = 1; i <= n; i++)
		up[i][0] = fa[i]; //跳2^0次是其父节点
	for (int j = 1; j <= 20; j++) {
    
     //构建倍增数组(用于快速找到温度达到界限的祖先)
		for (int i = 1; i <= n; i++) {
    
    
			up[i][j] = up[up[i][j - 1]][j - 1];
		}
	}
	for (int i = 1; i <= n; i++) {
    
    
		root[i] = ++tot;
		//     新版本   旧版本   温度界限 以dfs序的顺序构建主席树
		modify(root[i], root[i - 1], 1, 1e9, t[ord[i]]);
	}
	int q;
	cin >> q;
	while (q--) {
    
    
		int x, l, r;
		cin >> x >> l >> r;
		if (t[x] < l || t[x] > r) {
    
    
			cout << "0" << endl;
			continue;
		}

		for (int i = 20; i >= 0; i--) {
    
     //倍增找祖先
			if (t[up[x][i]] <= r)
				x = up[x][i];
		}					
		cout << query(root[out[x]], 1, 1e9, l, r) - query(root[in[x] - 1], 1, 1e9, l, r) << endl;             
	}

	return 0;
}










猜你喜欢

转载自blog.csdn.net/fdxgcw/article/details/119710665