题解:CF1784D Wooden Spoon
本文同步自洛谷专栏,题目传送门。
分析
初步观察
首先可以尝试考虑对于数 \(i\) 如何求解其答案。
整个比赛过程可以表示为如下图(以 \(n=4\) 为例):

此时 \(i\) 的位置是 \(-1\) 号结点,其余的结点 \(i\) 的子树大小为 \(2^i\),子树部分省略。
显然,每一颗子树内胜出的是子树内编号最小的结点,记作 \(a_i\)。
那么,如果 \(i\) 获得了木勺奖,应有 \(a_{-1}>a_0>a_1>a_2>a_3\),一个降序的结果。
不妨规定一个顺序,编号(子树大小)按从小到大排序,以后再对这部分作补偿。
形式化问题
将问题刻画为:将序列从前向后依次划分为长度为 \(1,1,2,4,\dots,2^{n-1}\) 的 \(n+1\) 段,记 \((2^i,2^{i+1}]\) 的最小值为 \(a_i\)(\(i\) 从 \(-1\) 开始且令 \(2^{-1}=0\)),有 \(a_{-1}>a_0>a_1>\dots>a_{n-1}\)。
那么可以记 \(dp_{i,j}\) 表示当前已经处理完编号为 \(i\) 的部分,当前最大值为 \(j\)。
然而,我们发现此时所求的结果需要成为初始状态,难以处理。
但是可以发现,\(a_{n-1}\) 应为 \(1\),即结束状态可能知道,于是将整个序列翻转过来考虑(对应编号、区间长度也翻转,特别注意区间下标范围的变化)。
此时,我们再来规定各个数算入贡献的时机:当第二维由 \(j\) 转移到 \(k\) 时(显然有 \(k>j\)),将 \(j+1,j+2,\dots,k-1\) 的贡献算入,要求为放在 \(k\) 所在段的前面。
还有一点值得注意的是:所在段的前面这一限制没有那么熟悉,那不妨规定最小值必须填在段首,最后再处理这部分贡献。
首先有初始状态 \(dp_{n-1,1}=1\),表示 \(1\) 必须填在整个序列开头。
可以列出转移方程:
\(dp_{i,k}=\sum_{j<k}\binom{r-j}{k-j-1}(k-j-1)!dp_{i+1,j}\),其中 \(r=\sum_{p=i+1}^{n-1}2^p\) 为第 \(i+1\) 到第 \(n-1\) 段的长度和。
组合数表示在 \(k\) 前面的空位个数中要填入 \(j+1,j+2,\dots,k-1\) 这些数,阶乘表示排列。
将组合数展开,\(dp_{i,k}=\sum_{j<k}\frac{(r-j)!}{(r-k+1)!}dp_{i+1,j}=\frac{1}{(r-k+1)!}\sum_{j<k}(r-j)!dp_{i+1,j}\)。
该转移方程容易前缀和优化,时间复杂度为 \(O(2^nn)\)。
最终统计时还需要对 \(dp_{-1,i}\) 将 \((i,2^n]\) 中的数的贡献算入,乘上阶乘表示排列。
此时再来讨论消除钦定的影响。
各段内部规定了最小值放在开头,实际上应可以任意放置,第 \(i\) 段需要补偿一个 \(2^i\),特别的,在最后的第 \(-1\) 段实际长度为 \(1\),不算入。总计为 \(2^{\frac{n(n-1)}{2}}\)。
各段之间也规定了顺序,实际上,编号 \(0,-1\) 两段可以交换,编号 \(0,-1\) 组成的整体可以与编号为 \(1\) 的段互换,依此类推,总计为 \(2^n\)。
实现
事实上,可以将补偿的贡献直接乘在 \(dp_{n-1,1}\) 上。
同时,可以滚动数组优化空间,还能够规避负数下标的问题。
至此,实现了时间复杂度 \(O(2^nn)\),空间复杂度 \(O(2^n)\)。
$\red{\text{code}}$
#pragma opimtize("O2")
#include<bits/stdc++.h>
using namespace std;
#define mod 998244353
#define N 1050000
#define rep(i,a,b) for(int i=a;i<=b;i++)
unsigned long long fac[N],inv[N];
int n,up,dp[N],sm[N];
inline int ksm(int a,int b){//快速幂
int ret=1;
for(;b;b>>=1,a=1ull*a*a%mod){
if(b&1) ret=1ull*a*ret%mod;
}
return ret;
}
inline void write(int x){
int stk[20],tp=0;
do{
stk[++tp]=x%10,x/=10;
}while(x);
while(tp) putchar('0'+stk[tp--]);
putchar('\n');
}
int main(){
scanf("%d",&n),up=1<<n,fac[0]=1,dp[1]=ksm(2,n*(n-1)/2+n);
for(int i=1;i<=up;i++) fac[i]=fac[i-1]*i%mod;
inv[up]=ksm(fac[up],mod-2);
for(int i=up-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
for(int i=n-1,r=0;i>=0;i--){
r|=(1<<i);//表示前方各段的长度和
rep(j,1,r) sm[j]=(sm[j-1]+dp[j]*fac[r-j])%mod;//对上一行做前缀和
rep(j,1,r+1) dp[j]=sm[j-1]*inv[r-j+1]%mod;
}
for(int i=1;i<=up;i++) write(dp[i]*fac[up-i]%mod);//注意将 >i 的贡献统计
return 0;
}

浙公网安备 33010602011771号