[多校赛20210621] 博弈

题目

Alice 和 Bob 在一棵有 \(n\) 个结点的树上玩游戏。

初始时,结点 1 上有一颗棋子。Alice 和 Bob 轮流移动棋子,移动时需要满足本次移动的起终点距离严格大于上一次的起终点距离,不能移动者输。双方都会以最优策略游戏。

现在 Alice 想要玩更多局游戏,她决定在树上选出一个包含结点 1 的连通子树,并进行游戏。那么,有多少种选树的情况使得 Bob 必胜呢?

数据范围:

一个测试点内有 \(T\) 组测试数据。

对于 \(100\%\) 的数据,满足 \(1\le n\le 2\times 10^5, 1\le T\le 10\)

代码

首先当然需要找出什么情况下后手必胜。

通过 特殊情况 + 打表 的方法可以发现:当且仅当树的直径包含偶数条边且结点 1 恰好在中点时才会出现后手必胜的情况。

说明:

此时先手如果将棋子移动到距离 1 为 \(d\) 的位置上,那么后手一定可以移动到另一个距离 1 为 \(d\),且不在同一子树内的位置上,且一定有合法的一个目标位置。由于移动距离不断增长,最终一定有一个位置使得先手无法移动。

而若不然,则若直径包含偶数条边,那么先手可以直接移动到直径中点;否则,可以移动到最靠近中心的任意一个点。无论如何,之后先手都可以复制后手的操作从而稳操胜券。

之后便是计算方案数,可以发现这等价了连通子树中以 1 为根时,最深叶子深度相同且不少于两个

自然可以考虑状态:

\(f_{u,k}\) 表示以 \(u\) 为根的子树内,包含了 \(u\) 且以 \(u\) 为根时最深叶子深度为 \(k\) 的方案数。

不难想到使用长链剖分优化,同时可以发现转移时相当于做第二维的 \(\max\) 卷积。如果将 \(v\) 并入 \(u\),当 \(k\) 不超过 \(v\) 的链长时,可以直接扫描并计算;否则就相当于做后缀乘法。充分利用长剖的扫描过程,我们可以直接在链上打标记,从头扫到尾的时候就可以顺便后放标记。

\(u=1\) 的时候需要额外计算答案,不过形式相近故略去。

时间复杂度就是 \(O(n)\)

小结:

  1. 对于这类寻找等价条件的题目,一定要熟练运用打表找规律的方法,尤其是没有明确思路的时候!
  2. 分析 DP 转移的时候最好将状态记下来,不要迷糊着乱写;
  3. 熟练运用标记思想,而不只在线段树等数据结构中。

代码

#include <cstdio>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

const int mod = 998244353;
const int MAXN = 2e5 + 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>
_T MAX( const _T a, const _T b )
{
	return a > b ? a : b;
}

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

int pF[MAXN], *ptrF = pF;
int pT[MAXN], *ptrT = pT;
int *f[MAXN], *tag[MAXN];

int dp[MAXN], dpTag[MAXN];
int head[MAXN], dep[MAXN], heavy[MAXN], mxDep[MAXN], L[MAXN];
int N, cnt;

inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
inline int Sub( int x, int v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }

int* AllocF( const int n ) { return ( ptrF += n ) - 1; }

int* AllocT( const int n ) 
{
	for( int i = 1 ; i <= n ; i ++ )
		* ( ptrT ++ ) = 1;
	return ptrT - 1;
}

void Clean()
{
	cnt = 0;
	rep( i, 1, N ) head[i] = 0;
	rep( i, 0, N ) dp[i] = 0, dpTag[i] = 1;
	for( ; ptrF != pF ; * ( ptrF -- ) = 0 ); *ptrF = 0;
	for( ; ptrT != pT ; * ( ptrT -- ) = 0 ); *ptrT = 0;
}

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

void Init( const int u, const int fa )
{
	mxDep[u] = dep[u] = dep[fa] + 1, heavy[u] = 0;
	for( int i = head[u], v ; i ; i = Graph[i].nxt )
		if( ( v = Graph[i].to ) ^ fa )
		{
			Init( v, u );
			if( mxDep[v] > mxDep[heavy[u]] )
				heavy[u] = v;
			mxDep[u] = MAX( mxDep[u], mxDep[v] );
		}
	L[u] = mxDep[u] - dep[u];
}

void DFS( const int u, const int fa, const int len )
{
	if( heavy[u] )
		DFS( heavy[u], u, len + 1 ), f[u] = f[heavy[u]] - 1, tag[u] = tag[heavy[u]] - 1;
	else f[u] = AllocF( len ), tag[u] = AllocT( len );
	f[u][0] = Add( f[u][0], 1 ); int tmp, lSu, rSu;
	for( int i = head[u], v ; i ; i = Graph[i].nxt )
		if( ( v = Graph[i].to ) ^ fa && v ^ heavy[u] )
		{
			DFS( v, u, 1 ), lSu = rSu = 1;
			for( int j = 1 ; j - 1 <= L[v] ; j ++ )
			{
				if( tag[v][j - 1] ^ 1 )
				{
					f[v][j - 1] = Mul( f[v][j - 1], tag[v][j - 1] );
					if( j <= L[v] ) tag[v][j] = Mul( tag[v][j], tag[v][j - 1] );
					tag[v][j - 1] = 1;
				}
				if( tag[u][j] ^ 1 )
				{
					f[u][j] = Mul( f[u][j], tag[u][j] );
					if( j < L[u] ) tag[u][j + 1] = Mul( tag[u][j + 1], tag[u][j] );
					tag[u][j] = 1;
				}
				if( ! fa )
				{
					if( dpTag[j] ^ 1 )
					{
						dp[j] = Mul( dp[j], dpTag[j] );
						if( j < N ) dpTag[j + 1] = Mul( dpTag[j + 1], dpTag[j] );
						dpTag[j] = 1;
					}
					dp[j] = Add( Mul( dp[j], rSu ), Mul( f[u][j], f[v][j - 1] ) );
				}
				tmp = f[u][j];
				f[u][j] = Add( Mul( f[u][j], f[v][j - 1] ), Add( Mul( f[u][j], rSu ), Mul( f[v][j - 1], lSu ) ) );
				lSu = Add( lSu, tmp ), rSu = Add( rSu, f[v][j - 1] );
			}
			if( L[v] + 2 <= L[u] ) 
				tag[u][L[v] + 2] = Mul( tag[u][L[v] + 2], rSu );
			if( ! fa && L[v] + 2 <= N )
				dpTag[L[v] + 2] = Mul( dpTag[L[v] + 2], rSu );
		}
}

int main()
{
	int T;
	read( T );
	while( T -- )
	{
		read( N ), Clean();
		rep( i, 1, N - 1 ) { int a, b;
			read( a ), read( b );
			AddEdge( a, b ), AddEdge( b, a );
		}
		Init( 1, 0 );
		DFS( 1, 0, 1 );
		int ans = 1;
		rep( j, 1, N )
		{
			if( dpTag[j] ^ 1 )
			{
				dp[j] = Mul( dp[j], dpTag[j] );
				if( j < N ) dpTag[j + 1] = Mul( dpTag[j + 1], dpTag[j] );
				dpTag[j] = 1;
			}
			ans = Add( ans, dp[j] );
		}
		write( ans ), putchar( '\n' );
	}
	return 0;
}
posted @ 2021-06-21 20:47  crashed  阅读(76)  评论(0编辑  收藏  举报