[HDU6334] Problem C. Problems on a Tree
题目
校内赛的改编题目。题意基本与[HDU6334] Problem C. Problems on a Tree相同。
分析
简单分析就可以发现,当 \(s\) 确定的时候,一个点 \(t\) 可以到达 \(s\) ,必须满足 \(t\) 到 \(s\) 的路径上,要么没有 3 边,要么仅有一条 3 边,且 \(s\) 到一端的路径上只有 1 边,\(t\) 到另一端的路径上没有 3 边。
“ 没有 3 边 ” 可以维护关于 1 边和 2 边的并查集,记为 \(T\) 。 “ \(s\) 到一端只有 1 边 ” 可以维护关于 1 边的并查集,记为 \(O\) 。
那么 \(s\) 可以到达 \(t\) 需要满足以下三条之一:
-
\(s\) 和 \(t\) 在 \(T\) 中同属一个集合。
-
\(s\) 所在的 \(O\) 集合和 \(t\) 所在的 \(T\) 集合之间仅有一条 3 边连接。
设 \(top_S(u)\) 表示 \(u\) 所在的 \(S\) 集合中最高的点,那么第二个条件要么是 \(top_O(s)\) 的父亲与 \(t\) 在 \(T\) 中同属一个集合,要么是 \(top_T(t)\) 的父亲与 \(s\) 在 \(O\) 中同属一个集合。
于是我们就可以解决第一个询问。
第二个问题中,我们同样可以转化为 \(s\) 所在的 \(T\) 集合的大小加上与 \(s\) 所在 \(O\) 集合仅有一条 3 边连接的所有 \(T\) 集合的大小的和。
第一个很好维护。但是第二个信息,由于相邻的 3 边可能很多,所以我们不能直接查询。
但是我们可以先处理一部分信息。在有根的情况下,最多有 1 条朝向根的 3 边。这一条我们可以单独统计。而其他的边都是朝向儿子的,因此我们可以直接维护这些边的贡献和,也就是维护所有与 \(O\) 相邻,且在子树内的 3 边连接的 \(T\) 集合的大小之和。
这样修改询问都可以做到 \(O(1)\) 。时间就是 \(O(n\log_2n)\) 或者 \(O(n\alpha(n))\) 的。
小结:
本题中对于相邻块信息的统计比较常用,其大体思路都是拆分成子树和父亲两个部分,并维护子树信息而查询父亲信息。这样的方法在[CF487E]Tourists也有运用。
代码
#include <map>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int MAXN = 3e5 + 5;
template<typename _T>
void read( _T &x )
{
x = 0; char s = getchar(); int f = 1;
while( s < '0' || '9' < s ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
while( '0' <= s && 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;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
void swapp( _T &x, _T &y )
{
_T t = x; x = y, y = t;
}
int dep[MAXN];
int N;
struct UFS
{
int fa[MAXN] = {}, big[MAXN] = {}, si[MAXN] = {};
void MakeSet( const int n ) { for( int i = 1 ; i <= n ; i ++ ) si[i] = 1, fa[i] = i; }
int FindSet( const int u ) { return fa[u] = ( fa[u] == u ? u : FindSet( fa[u] ) ); }
int GetSiz( const int u ) { return si[FindSet( u )]; }
void UnionSet( int u, int v )
{
u = FindSet( u ), v = FindSet( v );
if( u == v ) return ;
fa[u] = v, si[v] += si[u];
}
};
UFS One, OneTwo;
struct Edge
{
int to, nxt;
}Graph[MAXN << 1];
int key[MAXN];
int head[MAXN], c[MAXN], siz[MAXN], faE[MAXN], fath[MAXN];
int M, cnt = 1;
LL Id( const int u, const int v )
{ return 1ll * u * N + v; }
void AddEdge( const int from, const int to )
{
Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
head[from] = cnt;
}
void DFS( const int u, const int fa )
{
dep[u] = dep[fa] + 1, fath[u] = fa;
for( int i = head[u], v ; i ; i = Graph[i].nxt )
if( ( v = Graph[i].to ) ^ fa )
DFS( v, u ), key[v] = c[i >> 1];
}
void Init()
{
One.MakeSet( N ), OneTwo.MakeSet( N );
DFS( 1, 0 );
for( int i = 2 ; i <= N ; i ++ )
{
if( key[i] == 0 )
One.UnionSet( i, fath[i] ),
OneTwo.UnionSet( i, fath[i] );
if( key[i] == 1 )
OneTwo.UnionSet( i, fath[i] );
}
for( int i = 2 ; i <= N ; i ++ )
if( key[i] == 2 )
siz[One.FindSet( fath[i] )] += OneTwo.GetSiz( i );
}
int main()
{
read( N ), read( M );
for( int i = 1, a, b ; i < N ; i ++ )
read( a ), read( b ), read( c[i] ),
AddEdge( a, b ), AddEdge( b, a ), c[i] --;
Init();
int A, B, S, T, t, otS, otT;
while( M -- )
{
read( A ), read( B ), read( S ), read( T );
if( dep[A] > dep[B] ) swapp( A, B );
if( key[B] )
{
if( key[B] == 2 )
{
t = OneTwo.si[B];
siz[One.FindSet( fath[OneTwo.FindSet( A )] )] += t;
siz[One.FindSet( A )] -= t;
OneTwo.UnionSet( B, A );
}
if( key[B] == 1 )
siz[One.FindSet( A )] += siz[B], One.UnionSet( B, A );
key[B] --;
}
t = One.FindSet( S ), otS = OneTwo.FindSet( S ), otT = OneTwo.FindSet( T );
write( otS == otT || t == One.FindSet( fath[otT] ) || OneTwo.FindSet( fath[t] ) == otT ), putchar( ' ' );
int ans = OneTwo.si[otS] + siz[t];
if( key[t] == 2 ) ans += OneTwo.GetSiz( fath[t] );
write( ans ), putchar( '\n' );
}
return 0;
}