Solution -「多校联训」博弈
\(\mathcal{Description}\)
Link.
A B 两人在树上博弈,初始时有一枚棋子在结点 \(1\)。由 A 先操作,两人轮流移动沿树上路径棋子,且满足本次移动的树上距离严格大于上次的,无法移动者负。先给定一棵含 \(n\) 个结点的树,求包含结点 \(1\) 且使得 B 必胜的联通块数量。
\(n\le2\times10^5\)。
\(\mathcal{Solution}\)
结论对了正解写了细节萎了暴力分都没了 qwq……
结论:联通块满足条件,当且仅当其中最深的叶子们不同时属于结点 \(1\) 的某棵子树。当然也能说作,结点 \(1\) 是联通块直径的中点。
证:略。
“不同时属于”不太优美,考虑用所有方案减去非法方案。对于结点 \(1\) 的每棵子树,分别做长剖求出 \(f(v,d)\) 表示以 \(v\) 为根,最深叶子深度恰为 \(d\) 的联通块个数。随后钦定某棵子树取 \(d\),其余子树取严格小于 \(d\) 的方案,即能求出答案。
复杂度 \(\mathcal O(n)\)(其实我写得比较难看,会有一个求逆元的 \(\log\) awa……)
\(\mathcal{Code}\)
/* Clearink */
#include <list>
#include <cstdio>
#define rep( i, l, r ) for ( int i = l, rep##i = r; i <= rep##i; ++i )
#define per( i, r, l ) for ( int i = r, per##i = l; i >= per##i; --i )
typedef std::pair<int, int> PII;
#define fi first
#define se second
inline int rint() {
int x = 0, s = getchar();
for ( ; s < '0' || '9' < s; s = getchar() );
for ( ; '0' <= s && s <= '9'; s = getchar() ) x = x * 10 + ( s ^ '0' );
return x;
}
const int MAXN = 2e5, MOD = 998244353;
int n, ecnt, head[MAXN + 5];
int mxd[MAXN + 5], son[MAXN + 5];
PII *top, pool[MAXN * 5 + 10], *f[MAXN + 5];
// second 是乘法标记,转移过程中涉及到对长链的后缀乘法,需要打标记。
// 其实呢,由于出题人*****,更新时直接遍历长链也能过√
struct Edge { int to, nxt; } graph[MAXN * 2];
std::list<int> rt;
inline int sub( int a, const int b ) { return ( a -= b ) < 0 ? a + MOD : a; }
inline int add( int a, const int b ) { return ( a += b ) < MOD ? a : a - MOD; }
inline int mul( const long long a, const int b ) { return int( a * b % MOD ); }
inline int mpow( int a, int b ) {
int ret = 1;
for ( ; b; a = mul( a, a ), b >>= 1 ) ret = mul( ret, b & 1 ? a : 1 );
return ret;
}
inline void link( const int u, const int v ) {
graph[++ecnt] = { v, head[u] }, head[u] = ecnt;
graph[++ecnt] = { u, head[v] }, head[v] = ecnt;
}
inline PII* alloc( const int len ) {
PII* ret = top; top += len + 3;
return ret;
}
inline int init( const int u, const int fa ) {
int ret = 1;
mxd[u] = son[u] = 0;
for ( int i = head[u], v; i; i = graph[i].nxt ) {
if ( ( v = graph[i].to ) != fa ) {
ret = mul( ret, add( init( v, u ), 1 ) );
if ( mxd[u] < mxd[v] + 1 ) mxd[u] = mxd[son[u] = v] + 1;
}
}
// printf( "! all(%d)=%d\n", u, ret );
return ret;
}
inline void pushad( const int u, const int i, const int v ) {
f[u][i].fi = mul( f[u][i].fi, v );
if ( f[u][i].se ) f[u][i].se = mul( f[u][i].se, v );
else f[u][i].se = v;
}
inline void pushdn( const int u, const int i ) {
if ( f[u][i].se ) {
if ( i < mxd[u] ) pushad( u, i + 1, f[u][i].se );
f[u][i].se = 0;
}
}
inline void solve( const int u, const int fa, PII* curf ) {
f[u] = curf != NULL ? curf : alloc( mxd[u] + 1 );
f[u][0].fi = 1;
if ( son[u] ) solve( son[u], u, f[u] + 1 );
pushdn( u, 0 );
for ( int i = head[u], v; i; i = graph[i].nxt ) {
if ( ( v = graph[i].to ) != fa && v != son[u] ) {
solve( v, u, NULL );
int vpre = 0, upre = 1;
rep ( j, 0, mxd[v] ) {
pushdn( v, j ), pushdn( u, j + 1 );
if ( j <= mxd[v] ) vpre = add( vpre, f[v][j].fi );
int t = f[u][j + 1].fi;
f[u][j + 1].fi = add( mul( upre, f[v][j].fi ),
mul( f[u][j + 1].fi, add( vpre, 1 ) ) );
upre = add( upre, t );
}
if ( mxd[v] + 2 <= mxd[u] ) {
pushad( u, mxd[v] + 2, add( vpre, 1 ) );
}
}
}
}
inline void allClear() {
// mxd, son cleared in init.
ecnt = 0;
rep ( i, 1, n ) head[i] = 0;
for ( PII *p = pool; p <= top; *p++ = {} );
top = pool;
}
int main() {
freopen( "game.in", "r", stdin );
freopen( "game.out", "w", stdout );
for ( int T = rint(); T--; ) {
n = rint(), allClear();
rep ( i, 2, n ) link( rint(), rint() );
int all = init( 1, 0 ), del = 0;
for ( int i = head[1], v; i; i = graph[i].nxt ) {
solve( v = graph[i].to, 1, NULL );
rt.push_back( v );
rep ( j, 0, mxd[v] - 1 ) pushdn( v, j );
}
// printf( "! all=%d\n", all );
int las = 1;
rep ( i, 0, mxd[1] - 1 ) {
for ( int u: rt ) {
int k = !i ? 1 :
mul( las, mpow( add( f[u][i - 1].fi, 1 ), MOD - 2 ) );
del = add( del, mul( k, f[u][i].fi ) );
if ( i ) f[u][i].fi = add( f[u][i].fi, f[u][i - 1].fi );
}
for ( auto it( rt.begin() ); it != rt.end(); ) {
if ( i ) {
las = mul( las,
mpow( add( f[*it][i - 1].fi, 1 ), MOD - 2 ) );
}
las = mul( las, add( f[*it][i].fi, 1 ) );
if ( mxd[*it] == i ) it = rt.erase( it );
else ++it;
}
}
printf( "%d\n", sub( all, del ) );
}
return 0;
}
\(\mathcal{Details}\)
也不是说代码能力差吧,为什么我总能写那么长呢。
赛时代码比较狰狞(?)可以理解,但尽量先细致地过一遍代码流程,尽量写下来,确认无误再动手。
Solution -「多校联训」博弈的更多相关文章
- Solution -「多校联训」取石子游戏
\(\mathcal{Description}\) Link. 有 \(n\) 堆石子,第 \(i\) 堆有 \(x_i\) 个,Alice 每次只能从这堆中拿走 \(a_i\) 个石子,Bo ...
- Solution -「多校联训」排水系统
\(\mathcal{Description}\) Link. 在 NOIP 2020 A 的基础上,每条边赋权值 \(a_i\),随机恰好一条边断掉,第 \(i\) 条段的概率正比于 \(a ...
- Solution -「多校联训」I Love Random
\(\mathcal{Description}\) 给定排列 \(\{p_n\}\),可以在其上进行若干次操作,每次选取 \([l,r]\),把其中所有元素变为原区间最小值,求能够得到的所有不同序 ...
- Solution -「多校联训」签到题
\(\mathcal{Description}\) Link. 给定二分图 \(G=(X\cup Y,E)\),求对于边的一个染色 \(f:E\rightarrow\{1,2,\dots,c\ ...
- Solution -「多校联训」朝鲜时蔬
\(\mathcal{Description}\) Link. 破案了,朝鲜时蔬 = 超现实树!(指写得像那什么一样的题面. 对于整数集 \(X\),定义其 好子集 为满足 \(Y\sub ...
- Solution -「多校联训」消失的运算符
\(\mathcal{Description}\) Link. 给定长度为 \(n\) 的合法表达式序列 \(s\),其中数字仅有一位正数,运算符仅有 - 作为占位.求将其中恰好 \(k\) ...
- Solution -「多校联训」假人
\(\mathcal{Description}\) Link. 一种物品有 长度 和 权值 两种属性,现给定 \(n\) 组物品,第 \(i\) 组有 \(k_i\) 个,分别为 \((1,a ...
- Solution -「多校联训」古老的序列问题
\(\mathcal{Description}\) Link. 给定序列 \(\{a_n\}\),和 \(q\) 次形如 \([L,R]\) 的询问,每次回答 \[\sum_{[l,r]\su ...
- Solution -「多校联训」Sample
\(\mathcal{Description}\) Link (稍作简化:)对于变量 \(p_{1..n}\),满足 \(p_i\in[0,1],~\sum p_i=1\) 时,求 \(\ma ...
随机推荐
- [Beyond Compare] 排除/忽略 .svn 文件夹
[Beyond Compare] Exclude .svn folders Beyond Compare 3 Session >> Session Settings... >> ...
- 细谈 Java 匿名内部类 【分别 使用 接口 和 抽象类实现】
1.前言 匿名内部类是什么东西? 没有名字的内部类就是匿名内部类. 什么场景使用? 匿名内部类适合创建那种只需要一次使用的类. 这是个很有用的东西,可想而知,如果不使用匿名内部类,哪些只需要使用一次的 ...
- Linux上天之路(十)之Linux磁盘管理
主要内容 磁盘介绍 磁盘管理 磁盘限额 逻辑卷管理 磁盘阵列 1. 磁盘介绍 硬盘最基本的组成部分是由坚硬金属材料制成的涂以磁性介质的盘片,不同容量硬盘的盘片数不等.每个盘片有两面,都可记录信息.盘片 ...
- Go语言系列之标准库flag
Go语言内置的flag包实现了命令行参数的解析,flag包使得开发命令行工具更为简单. os.Args 如果你只是简单的想要获取命令行参数,可以像下面的代码示例一样使用os.Args来获取命令行参数. ...
- Java集合-LinkedList源码分析
目录 1.数据结构-链表 2.ArrayList结构特性 3.构造方法 4.成员变量 5.常用的成员方法 6.Node节点 7.序列化原理 8.迭代器 9.总结 1.数据结构-链表 链表(Linked ...
- manjaro20安装teamviewer出现sudo teamviewer –daemon start无响应
问题 https://www.randomhacks.co.uk/the-teamviewer-daemon-is-not-running-please-start-the-daemon-ubuntu ...
- golang中的标准库数据格式
数据格式介绍 是系统中数据交互不可缺少的内容 这里主要介绍JSON.XML.MSGPack JSON json是完全独立于语言的文本格式,是k-v的形式 name:zs 应用场景:前后端交互,系统间数 ...
- gin中如何记录日志和错误日志
package main import ( "github.com/gin-gonic/gin" "io" "os" ) func main ...
- 集合框架-工具类-Collections-其他方法将非同步集合转成同步集合的方法
集合框架TXT Collections-其他方法将非同步集合转成同步集合的方法
- 计算机/linux启动过程
开机过程指的是从打开计算机电源直到LINUX显示用户登录画面的全过程. 分析LINUX开机过程也是深入了解LINUX核心工作原理的一个很好的途径. 计算机开机过程是一个非常复杂的过程,想真正理解透彻并 ...