题解: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;
}

posted @ 2026-06-05 11:01  Wxb2010  阅读(8)  评论(0)    收藏  举报