CF914H Ember and Storm's Tree Game:解题报告:
NOIP 模拟赛 T3 3400*,哈哈。
题意
Alice 和 Bob 博弈,Alice 首先构造出一棵 (n) 个点,每个点度数不超过 (d) 的树 (T),Bob 选择树上两个点 ((u,v)),令序列 (a_1,a_2,cdots,a_k) 为有向路径每个点编号形成的序列。之后 Alice 可以选择一个 ([1,k-1]) 内的位置 (i),并进行一个操作 (optin{1,2})。如果 (opt=1),那么翻转 ([i+1,k]) 这个子序列并让这个序列加上 (a_i);如果 (opt=2),那么对 ([i+1,k]) 这个子序列取负并让这个序列加上 (a_i)。
如果最后的序列 (a) 是单调的,那么 Alice 获胜,否则 Bob 获胜。若两人都使用最佳策略,那么最后的方案会有多少种?(定义两种方案不同当且仅当 (T,u,v,i,opt) 有任意一个数不同)
(1leqslant d<nleqslant 200)。
分析
大毒瘤题。
首先考虑可以让 Alice 获胜的序列,我们发现它其实就是单峰/单谷序列,具体可以考虑一次操作可以消去最多一个峰/谷,而且一个单峰/谷序列一定有两种消法:
(R 代表翻转操作,I 代表取负操作)
于是 Alice 的目标转化为构造一个树使得树上的所有路径都是单峰/谷路径。
容易发现我们钦定边是从编号小的点连向大的点之后,合法的树一定存在一个点使得这个点为根之后所有从根出发的路径都是单向的(称这个点为特殊点),证明一下:
如果存在一个这样的点,那么你任取两个有祖孙关系的点的路径一定是有向的,那么任意两个点形成的路径最多一个峰/谷。
如果不存在这样一个点,那么你随便取个点找到它子树中那么冲突的位置,然后以那个位置为根再找到一个冲突的位置,我们可以发现这一定可以形成一个峰和谷数量大于 (1) 的序列。
于是我们只需要让 Alice 构造一棵合法的树就好了,对于每棵树,局面数量都有 (2n(n-1)) 种。(前面是操作方案数,后面是路径方案数)
我们设 (f_{i,j}) 表示子树大小为 (i),根度数为 (j) 的外向树方案数。(容易发现内向树是等价的)
这是一个简单 dp,我们只选择 ([1,i]) 内的编号,钦定根的编号为 (1),枚举编号为 (2) 的点作为儿子,注意合并要带一个组合数 ({i-2choose k-1}) 算插入的方案数:
这个 dp 很容易优化到 (O(n^3)),我们考虑怎么合并内向树和外向树:(枚举 (j,k) 分别为内向树的度数与外向树的度数)
但是这个会在有多个点是特殊点的情况算重,可以发现这样的情况特殊点会组成一条有向链,我们想要仅在链的底端计算贡献。
观察可以发现一个特殊点不在链的底端当且仅当它的 (k=1),于是我们算答案的时候强制 (k e 1) 就好了。
时间复杂度:(O(n^3)),听说可以用分治 NTT 做到 (O(n^2log n))。
代码
#include<stdio.h>
#define int long long
const int maxn=205;
int n,d,mod,ans;
int C[maxn][maxn],f[maxn][maxn],sum[maxn];
signed main(){
scanf("%lld%lld%lld",&n,&d,&mod);
for(int i=0;i<=n;i++){
C[i][0]=C[i][i]=1;
for(int j=1;j<i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
f[1][0]=sum[1]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<i&&j<=d;j++)
for(int k=1;k<i;k++)
f[i][j]=(f[i][j]+f[i-k][j-1]*C[i-2][k-1]%mod*sum[k])%mod;
for(int j=1;j<d;j++)
sum[i]=(sum[i]+f[i][j])%mod;
}
for(int i=1;i<=n;i++)
for(int j=0;j<=d;j++)
for(int k=0;j+k<=d;k++)
if(k!=1)
ans=(ans+f[i][j]*f[n-i+1][k])%mod;
printf("%lld
",2ll*n*(n-1)%mod*ans%mod);
return 0;
}