『MdOI R1』Treequery

题目

点这里看题目。

分析

如果这道题可以换根,那它就是一道水题,可是换不得。

我们首先考虑 \(p\) 是根的时候应该怎么做。可以发现,对于所有情况总存在:

\[\bigcap_{i=l}^rE(p,i)=E(p,\text{LCA}[l,r]) \]

这里我们认为 \(\text{LCA}[l,r]\) 为集合 \([l, r]\) 的点的 \(\text{LCA}\) ,括号决定区间的开或闭。

简单说明一下。记左边集合为 \(A\) ,右边为 \(B\) 。由于 \(\text{LCA}[l,r]\)\(l\sim r\) 的点的 \(\text{LCA}\) ,并且 \(p\) 就是根,所以 \(\text{LCA}[l,r]\) 必然在 \(p\)\(l\sim r\) 的公共路径的任意一个点上,就应该有 \(B\subset A\) 。又由于 \(\text{LCA}[l,r]\)\(l\sim r\)最深公共祖先,就有 \(A\subset B\) ,也就是 \(A=B\)

然后考虑 \(p\) 不是根的时候会怎么样。首先我们还是需要得到 \(u=\text{LCA}[l,r]\) 。然后考虑 \(u\)\(p\) 的位置,也可以理解为 " 换根 " :

\(\mathcal{Case 1:}\) \(p\)\(u\) 的祖先。此时可以像上面一样直接判断。

\(\mathcal{Case 2:}\) \(p\) 不是 \(u\) 的祖先,且 \(p\)\(l\sim r\) 的某些节点的祖先。此时如果将 \(p\) 提做根,那么 \(l\sim r\) 必然会分散到 \(p\) 的多个子树中,答案为 \(0\)

\(\mathcal{Case 3:}\) \(p\) 不是 \(l\sim r\) 中任何点的祖先。如果将 \(p\) 提做根,此时 \(l\sim r\)\(\text{LCA}\) 就不一定是 \(u\) 了。这里需要再分一些 \(\mathcal{Case}\)

\(\mathcal{Case3.1:}\) \(u\) 不是 \(p\) 作为根的情况下的 \(\text{LCA}\) 。那么我们应该多考虑哪一个节点呢?显然这个节点(不妨称之为 \(v\) )应该是 \(p\) 的祖先。更近一步的,应该是 \(v=\max_{i=l}^r\text{LCA}\{i,p\}\) (按照深度比较取大小)。对于 \(w\)\(v\) 的祖先,当 \(p\) 换做根之后,\(w\) 的子树内必然不会包含 \([l,r]\) 的所有点。

如果 \(u\) 不是 \(p\) 作为根的时候的 \(\text{LCA}\) ,就说明 \(p\)\(u\) 的子树内且 \(v\) 应该比 \(u\) 更深,正如下面这幅图:

\(\mathcal{Case3.2:}\) \(u\)\(p\) 作为根的情况下的 \(\text{LCA}\) 。此时 \(p\) 就不应该在 \(u\) 的子树内,且 \(v\)\(u\) 浅。如图:

这么分类讨论一下之后,问题就比较好解决了。我们只需要解决几个问题:

\(\mathcal{1.}\) 查询连续几个点的 \(\text{LCA}\) 。我们可以对点进行倍增, \(lca[i,j]\) 存储 \(\text{LCA}[i,i+2^j)\) 。由于 \(\text{LCA}\) 这个运算具有交换律、结合律和等幂性 ( 即 \(\text{LCA}\{a,a\}=a\) ),我们可以花费单次 \(\text{LCA}\) 的时间查询出连续几个点的 \(\text{LCA}\) 。如果使用 \(\text{RMQ}\) 查询 \(\text{LCA}\) 可以做到 \(O(n\log_2n)-O(1)\) 解决。

\(\mathcal{2.}\) 查询一个点是否是某些点的祖先,我们可以直接查询这个点内包含的标记点的数量。由于问题中标记点在一个区间,我们可以将树展开到 \(\text{DFS}\) 序上,并利用可持久化线段树维护点的前缀上 DFS 序的情况,并直接查询。

\(\mathcal{3.}\) 查询一个点到某些点的 \(\text{LCA}\) 中最深的那一个。显然我们可以从起始点进行倍增,每次检查是否有标记点在下一步的点的子树中,如果没有我们就可以跳一下。最终我们可以得到最浅的一个子树内不包含任何标记点的点,它的父亲就是我们的目标。这个过程可以配合倍增 \(\text{LCA}\) 理解。

时间复杂度是 \(O(n\log_2^2n)\) ,瓶颈是问题 \(\mathcal{3}\) ,占用了两个 \(\log\)

本题的可借鉴之处就在于它的分类讨论步骤和多次出现的倍增思想。树上换根问题,既可以 \(\text{LCT}\) 维护,也可以像这样分类讨论

代码

#include <cstdio>

const int MAXN = 2e5 + 5, MAXLOG = 20, MAXS = MAXN * MAXLOG;

template<typename _T>
void read( _T &x )
{
	x = 0;char s = getchar();int f = 1;
	while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
	while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
	x *= f;
}

template<typename _T>
void write( _T x )
{
	if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
	if( 9 < x ){ write( x / 10 ); }
	putchar( x % 10 + '0' );
}

struct edge
{
	int to, nxt, w;
}Graph[MAXN << 1];

int s[MAXS], lch[MAXS], rch[MAXS];
int rt[MAXN], nsiz;

int f[MAXN][MAXLOG], lca[MAXN][MAXLOG];
int lg[MAXN];

int head[MAXN], dist[MAXN], dep[MAXN], DFN[MAXN], siz[MAXN];
int N, Q, cnt, ID, lg2;

void addEdge( const int from, const int to, const int W )
{
	Graph[++ cnt].to = to, Graph[cnt].nxt = head[from], Graph[cnt].w = W;
	head[from] = cnt;
}

void addE( const int from, const int to, const int W )
{
	addEdge( from, to, W ), addEdge( to, from, W );
}

void DFS( const int u, const int fa )
{
	f[u][0] = fa;
	dep[u] = dep[fa] + 1, DFN[u] = ++ ID, siz[u] = 1;
	for( int i = head[u], v ; i ; i = Graph[i].nxt )
		if( ( v = Graph[i].to ) ^ fa )
			dist[v] = dist[u] + Graph[i].w, 
			DFS( v, u ), siz[u] += siz[v];
}

void balance( int &u, const int stp )
{
	for( int i = 0 ; ( 1 << i ) <= stp ; i ++ )
		if( stp & ( 1 << i ) )
			u = f[u][i];
}

int LCA( int u, int v )
{
	if( dep[u] > dep[v] ) balance( u, dep[u] - dep[v] );
	if( dep[v] > dep[u] ) balance( v, dep[v] - dep[u] );
	if( u == v ) return u;
	for( int i = lg2 ; ~ i ; i -- ) if( f[u][i] ^ f[v][i] ) u = f[u][i], v = f[v][i];
	return f[u][0];
}

int rangeLCA( int l, int r )
{
	int k = lg[r - l + 1];
	return LCA( lca[l][k], lca[r - ( 1 << k ) + 1][k] );
}

void upt( const int x ) { s[x] = s[lch[x]] + s[rch[x]]; }
void copy( int a, int b ) { s[a] = s[b], lch[a] = lch[b], rch[a] = rch[b]; }

int update( const int x, const int l, const int r, const int p )
{
	int cur = ++ nsiz; copy( cur, x );
	if( l == r ) { s[cur] ++; return cur; }
	int mid = l + r >> 1;
	if( p <= mid ) lch[cur] = update( lch[x], l, mid, p );
	else rch[cur] = update( rch[x], mid + 1, r, p );
	upt( cur ); return cur;
}

int query( const int x, const int l, const int r, const int segL, const int segR )
{
	if( ! x ) return 0;
	if( segL <= l && r <= segR ) return s[x];
	int mid = l + r >> 1, ret = 0;
	if( segL <= mid ) ret += query( lch[x], l, mid, segL, segR );
	if( mid < segR ) ret += query( rch[x], mid + 1, r, segL, segR );
	return ret;
}

int query( const int u, const int L, const int R )
{
	return query( rt[R], 1, N, DFN[u], DFN[u] + siz[u] - 1 )
	     - query( rt[L - 1], 1, N, DFN[u], DFN[u] + siz[u] - 1 );
}

void init()
{
	DFS( 1, 0 ); 
	
	lg[1] = 0;
	for( int i = 2 ; i <= N ; i ++ )
		lg[i] = lg[i - 1] + ( i == ( 1 << lg[i - 1] + 1 ) );
	lg2 = lg[N];
	
	for( int j = 1 ; j <= lg2 ; j ++ )
		for( int i = 1 ; i <= N ; i ++ )
			f[i][j] = f[f[i][j - 1]][j - 1];
	for( int i = 1 ; i <= N ; i ++ ) lca[i][0] = i;
	for( int j = 1 ; j <= lg2 ; j ++ )
		for( int i = 1 ; i + ( 1 << j - 1 ) <= N ; i ++ )
			lca[i][j] = LCA( lca[i][j - 1], lca[i + ( 1 << j - 1 )][j - 1] );
			
	for( int i = 1 ; i <= N ; i ++ )
		rt[i] = update( rt[i - 1], 1, N, DFN[i] );
}

int DCA( int u, const int L, const int R ) // Deepest Common Ancestor
{
	int tmp;
	for( int i = lg2 ; ~ i ; i -- )
		if( tmp = f[u][i] )
			if( ! query( tmp, L, R ) )
				u = tmp;
	return f[u][0];
}

int getDist( const int u, const int v )
{
	return dist[u] + dist[v] - 2 * dist[LCA( u, v )];
}

int main()
{
	read( N ), read( Q );
	for( int i = 1, a, b, c ; i < N ; i ++ )
		read( a ), read( b ), read( c ), addE( a, b, c );
	init();
	
	int P, L, R, lst = 0;
	while( Q -- )
	{
		read( P ), read( L ), read( R );
		P ^= lst, L ^= lst, R ^= lst;
		
		int RLCA = rangeLCA( L, R ), 
			num = query( P, L, R );
		if( num == R - L + 1 ) lst = getDist( P, RLCA );
		else if( num ) lst = 0;
		else
		{
			int t = DCA( P, L, R );
			if( dep[t] > dep[RLCA] ) lst = getDist( P, t );
			else lst = getDist( P, RLCA );
		}
		write( lst ), putchar( '\n' );
	}
	return 0;
}
posted @ 2020-08-07 22:19  crashed  阅读(148)  评论(0编辑  收藏  举报