1. 程式人生 > 實用技巧 >『MdOI R1』Treequery

『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\) 的任意一個點上,就應該有 \(A\subset B\) 。又由於 \(\text{LCA}[l,r]\)\(l\sim r\) 的最深的的公共祖先,就有 \(B\subset A\) ,也就是 \(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;
}