[CF1062E]Company

题目

传送门 to luogu

思路

确实是数据结构学傻了! O n l y    s t u d y    n o t    p l a y ,    m a k e s    J a c k    a    d u l l    b o y . \tt Only\; study\; not\; play,\; makes\; Jack\; a\; dull\; boy.

数据结构硬上

考虑一个 l c a \tt lca ,至少有两个点分属于不同的儿子子树。如果它只有两个儿子子树,并且其中一个内部只有一个点,则删去之可以使 l c a \tt lca 更低。即:

在这里插入图片描述

右边的“唯一点”删去之后是有点作用的。而左边的多个点无论删去哪个都没用。特判掉两个点(两个子树内都是只有一个点)的情况。多个子树时,无用。 l c a \tt lca 本身是一个被选中的点,则必删。

感觉并不是很好直接使用线段树,因为 l c a \tt lca 变来变去。那就 把询问离线,这样便于我们甩掉一个维度。莫队?算了。还是按照右端点排序。

利用 动态 d p \tt dp 的思想,我们树链剖分,轻儿子暴力改,重儿子统一改。现在我们要维护的无非就是编号不小于 l l 的所有点,属于某个点的哪些子树。

根据以往的套路,对于每颗子树存 编号最大的一个点,以此检测区间。这样一来,对于一个 l c a \tt lca ,若有至少三个儿子的“代表点”是不小于 l l 的,删谁都无所谓,可以输出答案。否则,我们可以试着删去其中一个子树的其中一个点,因为只有两颗子树了。

所以,对于一个点,将它的轻儿子用可删堆维护最大的 3 3 个。当我们的询问右端点从 r 1 r-1 变成 r r 时,将 r r 到根路径上的所有点都修改一下,重链上的直接整体赋值,链顶由于是一个轻儿子,将其父亲修改。

复杂度是 O ( n log 2 n ) \mathcal O(n\log^2 n) 的。其实应该还好,因为可删堆里面的元素应该不会很多。如果会,当我没说。 不过树剖的整体赋值似乎也是这个复杂度。

学傻了的我

看看题解,学到一个冷知识,只需要删除 d f n \tt dfn 最小或最大的点。

然后我辛辛苦苦地打了 s t \tt st 表维护 l c a \tt lca ,用的 t a r j a n \tt tarjan 以求做到线性,故 O ( n log n ) \mathcal O(n\log n)

还是有点常数的。代码将会在下方展示。

优美的解法

去证明上面那个冷知识即可。那就是,很多点的 l c a \tt lca 就是这些点中 d f n \tt dfn 最小和最大的两个点的 l c a \tt lca

怎么证明?可以用“子树内的 d f n \tt dfn 连续”证明。记 d f n \tt dfn 最小和最大的两个点分别为 a , b a,b ,显然 l c a \tt lca 子树的 d f n \tt dfn 区间是包含 [ d f n a , d f n b ] [{\tt dfn}_a,{\tt dfn}_b] 。于是 d f n \tt dfn 在二者之间的一定也在 l c a \tt lca 子树中。于是 l c a \tt lca 铁定不变。

事实上,这早就在讲“虚树”时证明过了。

既然如此,用什么 s t \tt st 表啊?直接找到 d f n \tt dfn 最小和最大的两个点,求一个 l c a \tt lca ,不香吗?

代码 o f    2 n d    m e a n s \tt of\; 2^{nd}\;means

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 100005;
int n, m;

struct Edge{
	int to, nxt; Edge(){}
	Edge(int T,int N){
		to = T, nxt = N;
	}
};
Edge e[MaxN<<1];
int cntEdge, head[MaxN];
void clear_graph(){
	for(int i=1; i<=n; ++i)
		head[i] = -1;
	cntEdge = 0;
}
void addEdge(int a,int b){
	e[cntEdge] = Edge(b,head[a]);
	head[a] = cntEdge ++;
}

int dep[MaxN]; // 每个点的深度
int fa[MaxN][20]; // 倍增数组
int dfn[MaxN], dfsClock; // dfs序
void build(int x,int pre = 0){
	dfn[x] = ++ dfsClock;
	dep[x] = dep[pre]+1;
	fa[x][0] = pre; // 简化递归过程
	for(int j=0; j+1<20; ++j){
		if(fa[x][j] == 0) break;
		fa[x][j+1] = fa[fa[x][j]][j];
	}
	for(int i=head[x]; ~i; i=e[i].nxt)
		if(e[i].to != pre)
			build(e[i].to,x);
}
int getLca(int a,int b){
	if(!a || !b) return a+b;
	if(dep[a] < dep[b]) swap(a,b);
	for(int j=19; ~j; --j)
		if((dep[a]-dep[b])>>j&1)
			a = fa[a][j];
	if(a == b) return a;
	for(int j=19; ~j; --j)
		if(fa[a][j] != fa[b][j])
			a = fa[a][j], b = fa[b][j];
	return fa[a][0];
}

namespace UFS{
	int fa[MaxN], rnk[MaxN];
	int val[MaxN]; // “根”节点
	void init(){
		for(int i=1; i<=n; ++i){
			fa[i] = val[i] = i;
			rnk[i] = 1; // itself
		}
	}
	inline int find(int a){
		if(a != fa[a])
			fa[a] = find(fa[a]);
		return fa[a];
	}
	void combine(int a,int b){
		a = find(a), b = find(b);
		if(rnk[a] > rnk[b]) swap(a,b);
		if(dep[val[a]] < dep[val[b]])
			val[b] = val[a]; // 最浅点
		fa[a] = b, rnk[b] += rnk[a];
	}
}
vector< int > q[MaxN];
bool vis[MaxN]; // 是否已经访问
void tarjan(int x,int pre = 0){
	for(int i=head[x]; ~i; i=e[i].nxt)
		if(e[i].to != pre){
			tarjan(e[i].to,x);
			UFS::combine(e[i].to,x);
		}
	vis[x] = true; // 防止 祖先-后代
	int len = q[x].size();
	for(int i=0,y; i<len; ++i)
		if(vis[y = q[x][i]]){
			y = UFS::find(y);
			q[x][i] = UFS::val[y];
		}
		else q[x][i] = -1;
}

int lca[20][MaxN], lg2[MaxN];
int st1[20][MaxN]; // 最小 dfn
int st2[20][MaxN]; // 最大 dfn
void prepare(){
	lg2[1] = 0; // 2^0 = 1
	for(int i=2; i<=n; ++i)
		lg2[i] = lg2[i>>1]+1;
	for(int i=1; i<=n; ++i){
		st1[0][i] = st2[0][i] = i;
		lca[0][i] = i; // itself
	}
	for(int j=1; (1<<j)<=n; ++j){
		int len = (1<<j>>1);
		for(int i=1; i+len*2-1<=n; ++i){
			int a = lca[j-1][i];
			int b = lca[j-1][i+len];
			q[a].push_back(b);
			q[b].push_back(a);

			a = st1[j-1][i];
			b = st1[j-1][i+len];
			if(dfn[a] < dfn[b])
				st1[j][i] = a;
			else st1[j][i] = b;

			a = st2[j-1][i];
			b = st2[j-1][i+len];
			if(dfn[a] > dfn[b])
				st2[j][i] = a;
			else st2[j][i] = b;
		}
		for(int i=1; i<=n; ++i)
			vis[i] = false;
		UFS::init(); tarjan(1);
		for(int i=n-len*2+1; i; --i){
			int a = lca[j-1][i];
			int b = lca[j-1][i+len];
			if(q[a].back() != -1)
				lca[j][i] = q[a].back();
			else lca[j][i] = q[b].back();
			q[a].pop_back();
			q[b].pop_back();
		}
	}
}
int queryLca(int l,int r){
	if(l > r) return 0;
	int k = lg2[r-l+1];
	if((1<<k) == r-l+1)
		return lca[k][l];
	return getLca(lca[k][l],
		lca[k][r-(1<<k)+1]);
}
pair< int,int > queryDfn(int l,int r){
	pair< int,int > res;
	int k = lg2[r-l+1], a, b;

	a = st1[k][l];
	b = st1[k][r-(1<<k)+1];
	if(dfn[a] > dfn[b]) a = b;
	res.first = a;

	a = st2[k][l];
	b = st2[k][r-(1<<k)+1];
	if(dfn[a] < dfn[b]) a = b;
	res.second = a;

	return res;
}

int main(){
	n = readint(), m = readint();
	clear_graph();
	for(int i=2; i<=n; ++i)
		addEdge(readint(),i);
	build(1), prepare();
	for(int l,r; m; --m){
		l = readint(), r = readint();
		auto away = queryDfn(l,r);
		int xjh = queryLca(l,away.first-1);
		xjh = getLca(xjh,queryLca(away.first+1,r));

		int xez = queryLca(l,away.second-1);
		xez = getLca(xez,queryLca(away.second+1,r));

		if(dep[xjh] < dep[xez])
			xjh = xez, away.first = away.second;
		printf("%d %d\n",away.first,dep[xjh]-1);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42101694/article/details/108194691