2021牛客多校7F xay loves trees (线段树+括号化区间)

拒绝中国大学生英语阅读理解大赛!
打得最快乐的一场,因为没有谜语人出题
题目大意:给两个有n个节点n-1个边的有向树,让你选择一些点,使得
1:在第一个树上,这些点是连通的,而且一个点必定是另一个点的祖先
2:在第二个树上,任意两个点互相都不是祖先
第一个树就要求了这些点刚好刚好构成一个链,而这些点在第二个树中不能在同一个链里

思路是:从第一个树上的某个点开始深搜,新搜索到的点往回走,一直走到与当前已搜索到的某个点在第二棵树中位于同一个链上,记录两点间距离.
(有点乱,建议先往下看)
所以我们可以用线段树来维护来维护括号化序列
首先对第二个树进行dfs搜索,记录下每个点入栈出栈的时间

void dfs(int u, int fa) {
    
     
	in[u] = ++stamp;
	for (auto next : node2[u]) {
    
    
		if (next == fa)
			continue;
		dfs(next, u);
	}
	out[u] = ++stamp;
}

文字无用,还是画图来的直观(等我有钱了一定买个手绘板)
请添加图片描述
假设 1 ,2 分别是题目中的两个树,我们对2号树进行dfs,获取其出入栈的时间戳
接着,我们把这些时间戳放在一个时间轴上请添加图片描述
假设我们现在想要知道,在1号图中以4号点开始向源点方向延伸所连成的合法链最长可以有多长
如图,与4号点的区间出现重叠的点(1号点和2号点),在2图中他们呈现祖先关系,他们与题目出现冲突,而3号点与4号点的区间没有出现重叠,他们在2图中没有祖先关系,也就没有冲突.
如果将4号点往1号点的方向去看(4->3->2->1),2号点是第一个与4号点出现冲突的点.
在1号图中,2号点的深度是2,4号点深度是4,所以答案就是4-2=2;最长可以连出一条长度为2的合法链4->3
所以我们对图1进行dfs,每搜一个新点,就往回找,与当前点产生冲突的,深度最深的点的深度是多少,随后当前点深度减去冲突点深度,随后与当前答案取一个max;
维护上图中的时间轴我们就可以用线段树来进行维护.

我们用vector<int>tr[N]来作为线段树
mx[N]:当前线段树区间和它的子区间中最大深度是多少
tr[u][i]表示在u这段里,第i个存入的点
et3_tsy大佬的视频

#include <bits/stdc++.h>
using namespace std;
int n, ans;
const int N = 3e5 + 10;

//用线段树维护出入时间
int mx[N << 4]; //当前线段树区间和它的子区间中最大深度是多少
vector<int>tr[N << 4]; //线段树,距离在这个区间里出现了哪些节点
int in[N], out[N];
vector<int>node[N], node2[N];
int d[N], stamp;

void init() {
    
     //初始化
	for (int i = 0; i <= n; i++)
		node[i].clear(), node2[i].clear(), in[i] = 0, out[i] = 0, d[i] = 0;
	ans = 1;
	stamp = 0;
	for (int i = 0; i <= (n << 3); i++)
		tr[i].clear(), mx[i] = 0;
	d[1] = 1;
}

void dfs(int u, int fa) {
    
     //将第二个树进行括号化序列
	in[u] = ++stamp;
	for (auto next : node2[u]) {
    
    
		if (next == fa)
			continue;
		dfs(next, u);
	}
	out[u] = ++stamp;
}

void pushup(int u, int l, int r) {
    
    
	mx[u] = 0;
	if (tr[u].size())
		mx[u] = *(tr[u].end() - 1); //如果已经存过数了,就把最后放入,也是最深的点储进去
	if (l < r) {
    
     //维护当前区间的两个子区间中最大的深度是多少
		mx[u] = max(mx[u], mx[u << 1 | 1]);
		mx[u] = max(mx[u], mx[u << 1]);
	}
}

//                当前节点的范围  点的范围
void update(int u, int l, int r, int chl, int chr, int val) {
    
    	
	if (chl > r || chr < l)
		return ;
	if (chl <= l && chr >= r) {
    
    
		if (val > 0) {
    
    //当v为正数,代表维护一个新节点进来,维护其深度
			//由于dfs时是至上而下搜的,所以最大值一定是最后一个数
			tr[u].push_back(d[val]);//将vector当作线段树用
		} else {
    
    //否者就是删除一个点
			tr[u].pop_back();
		}
		pushup(u, l, r);
		return ;
	}
	int mid = (l + r) >> 1;
	update(u << 1, l, mid, chl, chr, val);
	update(u << 1 | 1, mid + 1, r, chl, chr, val);
	pushup(u, l, r);

}


int query(int u, int l, int r, int chl, int chr) {
    
    
	int res = 0;
	if (chl > r || chr < l)
		return 0;
	if (tr[u].size())
		res = max(res, *(tr[u].end() - 1)); //当前区间覆盖元素的最大值
	if (chl <= l && chr >= r) {
    
     //完全匹配了,就返回当前区间最深节点的深度
		return mx[u];
	}
	int mid = (l + r) >> 1;
	res = max(res, query(u << 1, l, mid, chl, chr));
	res = max(res, query(u << 1 | 1, mid + 1, r, chl, chr));
	return res;

}


void dfs2(int u, int fa, int dis) {
    
     

	dis = max(dis, query(1, 1, 2 * n, in[u], out[u])); 
	//询问与它产生冲突的点中最深的点
	
	ans = max(ans, d[u] - dis); 
	//用当前点的深度减去会产生冲突点的深度就是从这个点往回走所能走的最远距离
	
	update(1, 1, n << 1, in[u], out[u], u);

	for (auto next : node[u]) {
    
    
		if (next == fa)
			continue;
		d[next] = d[u] + 1;
		dfs2(next, u, dis);
	}
	update(1, 1, 2 * n, in[u], out[u], -u); //回溯

}

void solve() {
    
    

	cin >> n;
	init();
	for (int i = 2; i <= n; i++) {
    
    
		int x, y;
		cin >> x >> y;
		node[x].push_back(y);
		node[y].push_back(x);
	}
	for (int i = 2; i <= n; i++) {
    
    
		int x, y;
		cin >> x >> y;
		node2[x].push_back(y);
		node2[y].push_back(x);
	}
	dfs(1, 0); //括号化序列,记录出入时dfs序
	dfs2(1, 0, 0);
	cout << ans << endl;

}

int main() {
    
    
	int t;
	cin >> t;
	while (t--) {
    
    
		solve();
	}
	return 0;
}







猜你喜欢

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