4.8模拟赛 C

A 没啥意思 B 不会 nim 积 就不写题解了。

我们考虑什么时候先手必赢。我们分直径的奇偶来讨论。

如果直径长度是奇,那么一定存在一个直径中点。假设开始的时候棋子在直径中点,那么先手不管怎么跳,后手都可以跳到直径上与终点对称的那个方向,于是先手就 GG 了。否则,假设开始不再直径中点,先手显然可以一次让棋子到直径中点,后手就 GG 了。

如果直径长度是偶,那么这个时候相当于有两个终点。先手可以跳到离根较远的那个中点上,于是后手必然不能通过模仿棋走到另一个中点,这个时候先手又可以和后手下模仿棋了。

所以得出结论,如果直径为偶,先手必胜。如果直径为奇,那么当根为直径中点的时候,后手必胜,否则先手必胜。

这个东西看起来后手必胜比较好统计,但是实际想一下发现甚至需要考虑直径通过根不是很好弄。所以考虑统计先手必胜的情况。

先手必胜,那么一定只在某一棵子树中存在最深的节点,其他子树的节点的深度肯定都不如它。如果有两个子树的最深节点相同并且作为全局最深的节点,那么它们到根一定构成了一条直径,并且根还作为了中点,就爆炸了。如果满足了只在某一棵子树中存在最深的节点,那么要么直径在某个子树内,要么直径虽然穿过根,但是不可能以根作为中点。所以我们现在要统计的就是,以某个子树内最深的节点作为全局最深的节点,且其他子树最深节点都不如它的方案数量。

这个东西可以 dp 解决。设 $dp[u][i]$ 表示 $u$ 子树内最深节点深度为 $i$ 的方案数量。那么最后合并的时候就枚举下深度,再枚举下 $dp[v][dep - 1]$ 不为 $0$ 的儿子 $v$ ,然后前缀和一下算答案就好了。

我们考虑这个 dp 如何转移。设当前根为 $u$ ,$sum[u]$ 是 $dp[u]$ 的前缀和,那么我们考虑,要么在这之前最深深度还不到 $i$ ,加入了个深度为 $i-1$ 的子树,要么在这之前深度已经是 $i$ ,加入的深度可以是 $1\sim i-1$ 任何数。

我们考虑长链剖分来合并。合并的过程中,我们做一个前缀和。对于当前这个链比合并的链长的部分(一段后缀)我们需要做的实际上是乘上 $1 + sum[v]$ 。这个东西可以打标记解决。

统计答案的时候,我们记 $S[x]$ 表示包括 $1$ 的深度小于 $x$ 的方案数,我们知道 $S[x]$ 就是所有子树内 最大深度为 $1\sim x-1$ 的方案数量和 的积。如果我们想要钦定最深的点在 $v$ 这个子树中,我们枚举深度 $d$ ,方案数量就是 $\frac{S[d]}{sum[v][d-1] + 1}\times dp[v][d]$ ,因为我们要去除深度为小于 $d$ 的情况,再乘上任选择一个深度为 $d$ 的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "cmath"
#include "vector"
#include "map"
#include "set"
#include "queue"
using namespace std;
#define MAXN 1000006
//#define int long long
#define rep(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++i)
#define per(i, a, b) for (int i = (a), i##end = (b); i >= i##end; --i)
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define all(x) (x).begin() , (x).end()
#define mem( a ) memset( a , 0 , sizeof a )
#define P 998244353
typedef long long ll;
int n , A[MAXN];
vi G[MAXN];

int Pow( int x , int a ) {
int ret = 1;
while( a ) {
if( a & 1 ) ret = 1ll * ret * x % P;
x = 1ll * x * x % P , a >>= 1;
}
return ret;
}

int hea[MAXN] , mxd[MAXN];
void dfs( int u , int fa ) {
mxd[u] = 1;
for( int& v : G[u] ) if( v != fa ) {
dfs( v , u );
if( !hea[u] || mxd[v] > mxd[hea[u]] ) hea[u] = v , mxd[u] = mxd[v] + 1;
}
}

int dp[MAXN * 2] , tg[MAXN * 2] , po[MAXN * 2] , tl[MAXN * 2] , cnt;
inline void pd( int x ) {
if( tg[x] != 1 ) {
if( !tl[x] ) {
dp[x + 1] = 1ll * dp[x + 1] * tg[x] % P;
tg[x + 1] = 1ll * tg[x + 1] * tg[x] % P;
}
tg[x] = 1;
}
}
inline void mul( int x , int c ) {
dp[x] = 1ll * dp[x] * c % P , tg[x] = 1ll * tg[x] * c % P;
}
void wkr( int u , int fa ) {
po[u] = ++ cnt , dp[cnt] = 1; // to guarantee the index is continuous
if( hea[u] ) wkr( hea[u] , u ); else tl[po[u]] = 1;
int S , s , t;
for( int v : G[u] ) if( v != fa && v != hea[u] ) {
wkr( v , u );
S = dp[po[u]] , s = 0;
pd( po[u] );
rep( i , 0 , mxd[v] - 1 ) {
pd( po[u] + i + 1 ) , pd( po[v] + i );
s = ( s + dp[po[v] + i] ) % P;
t = dp[po[u] + i + 1];
dp[po[u] + i + 1] = ( ( t + 1ll * S * dp[po[v] + i] % P ) % P + 1ll * s * t % P ) % P;
S = ( S + t ) % P;
}
mul( po[u] + mxd[v] + 1 , s + 1 );
}
}

int S[MAXN] , ttg[MAXN];
int Just_DOIT( ) {
fill( ttg , ttg + MAXN - 1 , 1 );
fill( tg , tg + MAXN * 2 - 1 , 1 );
fill( S , S + MAXN - 1 , 1 );
po[1] = ++ cnt;
if( hea[1] ) wkr( hea[1] , 1 );
for( int v : G[1] ) {
if( v != hea[1] ) wkr( v , 1 );
int s = 0;
rep( i , 0 , mxd[v] - 1 ) {
pd( po[v] + i );
s = ( s + dp[po[v] + i] ) % P;
S[i + 1] = 1ll * S[i + 1] * ( 1 + s ) % P;
}
ttg[mxd[v] + 1] = 1ll * ttg[mxd[v] + 1] * ( s + 1 ) % P;
}
rep( i , 0 , mxd[1] )
S[i] = 1ll * S[i] * ttg[i] % P , ttg[i + 1] = 1ll * ttg[i] * ttg[i + 1] % P;
int re = 0 , s = 0;
for( int v : G[1] ) {
s = 0;
rep( i , 0 , mxd[v] - 1 ) {
re = ( re + 1ll * S[i] * Pow( s + 1 , P - 2 ) % P * dp[po[v] + i] % P ) % P;
s = ( s + dp[po[v] + i] ) % P;
}
}
return re;
}

void solve() {
cin >> n;
int u , v;
rep( i , 2 , n ) {
scanf("%d%d",&u,&v) , G[u].pb( v ) , G[v].pb( u );
}
dfs( 1 , 1 );
cout << Just_DOIT( ) << endl;
}

signed main() {
// freopen("in1.in","r",stdin);
// freopen("fuckout","w",stdout);
// int T;cin >> T;while( T-- ) solve();
solve();
}
文章作者: yijan
文章链接: https://yijan.co/48-mo-ni-sai-c/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Yijan's Blog