1 条题解
-
0
E. Quantifier 详细题解
这是一道树型DP + 组合计数 + 生成函数合并的压轴题,核心是把操作规则转化为芯片的层级约束,再通过树上DP统计合法排列数。
一、题目核心规则解读
1. 树与边的定义
- 树以为根,的唯一儿子是;
- 节点对应父节点到的边,所有芯片初始在边。
2. 合法操作(关键!)
- 沿链移动:芯片只能在根到的路径边上移动,不能越界;
- 同色交换:同一条边上相邻同色芯片可以任意交换;
- 最终要求:所有芯片回到边,求合法排列数。
3. 核心等价结论
- 每个芯片会被绑定到树的一个节点上(该节点是它能到达的最深公共节点);
- 芯片只能和同绑定节点、同色的芯片自由排列;
- 不同绑定节点的芯片按树的父子关系有序组合。
二、核心步骤
步骤1:芯片绑定(最关键)
对每个芯片:
- 找到根到的路径节点链;
- 从深到浅找第一个允许放置该颜色的节点,将芯片绑定到;
- 用存储节点上的所有芯片颜色。
步骤2:预处理组合数与阶乘
预处理组合数和阶乘,模:
$$C[i][j] = C[i-1][j-1] + C[i-1][j],\quad \text{fac}[i] = \text{fac}[i-1] \times i \bmod p $$- 组合数:用于合并子树方案数;
- 阶乘:用于同节点同色芯片的全排列。
步骤3:树型DP定义
:以为根的子树中,最终保留最上方颜色为、总长度为的合法序列方案数。
- :芯片颜色;
- :序列长度。
步骤4:子树合并(核心DP转移)
合并两个子树和,分三种情况:
- 异色合并(diff):顶部颜色不同,直接用组合数拼接;
- 同色部分合并(same1):中间同色段交叉排列;
- 同色尾部合并(same):末尾同色段拼接。
合并后更新,完成子树信息聚合。
步骤5:当前节点芯片插入
对绑定在上的芯片:
- 同色段内部:阶乘乘法(全排列);
- 插入到子树序列上方/下方:组合数计算合法插入方式;
- 更新和子树大小。
步骤6:答案统计
根节点的所有求和,即为总合法排列数:
$$\text{ans} = \sum_{k=1}^m \big(f[1][0][k] + f[1][1][k]\big) \bmod 998244353 $$
三、标程代码逐段解析
1. 全局变量定义
const int p=998244353; int fa[5005],col[5005],d[5005]; // 父节点、芯片颜色、移动范围 int C[5005][5005],fac[5005]; // 组合数、阶乘 int f[5005][2][5005]; // 树型DP数组 vector<int> to[5005],vec[5005]; // 树边、节点绑定芯片- 维度完全匹配题目的限制。
2. 子树合并函数
(1)异色合并 diff
inline void diff(int u,int v){ int S=siz[u],T=siz[v]; for(int x=0;x<2;x++){ for(int i=S;i>=1;i--){ g[x][i]=(g[x][i]+(1LL*A*C[S+T-i-1][T-1]+1LL*f[u][x][i]*C[S+T-i][T])%p*B)%p; } } }- 公式含义:顶部颜色固定,用组合数计算两个异色序列的合法拼接方案。
(2)同色合并 same
inline void same(int u,int v){ same1(u,v),same1(v,u); for(int x=0;x<2;x++) g[x][S+T]=(g[x][S+T]+1LL*f[u][x][S]*f[v][x][T]%p*C[S+T][T])%p; }- 同色序列可以完全交叉排列,贡献种方案。
3. DFS主逻辑
void dfs(int u){ // 1. 递归处理所有子树 for(auto v:to[u]){ dfs(v); merge(u,v); } // 2. 插入当前节点绑定的芯片 if(vec[u].empty())return; // 3. 同色段计算阶乘,更新DP数组 int prd=1,L=vec[u].size(); for(int i=0,j=0;i<L;i=j){ while(j<L&&vec[u][i]==vec[u][j])j++; prd=1LL*prd*fac[j-i]%p; } // 4. 插入到DP序列中 siz[u]+=L; }- 先合并子树→再插入当前节点芯片→完成整棵子树DP。
4. 主函数流程
- 多组测试用例输入;
- 建树+芯片绑定;
- 预处理组合数、阶乘;
- DFS(1)跑树DP;
- 统计答案输出。
四、复杂度分析
- 预处理:;
- 树DP:;
五、核心公式总结
- 同色排列:长度为的同色段贡献;
- 序列拼接:长度的序列拼接贡献;
- 答案:根节点DP数组求和:
六、标程正确性验证(样例1)
输入:
3 2 0 1 1 0 1 2 3- 芯片1绑定节点2,芯片2绑定节点3;
- 两芯片异色、无约束,可自由交换;
- 答案:,与样例输出一致。
七、完整标程代码
#include <bits/stdc++.h> #define eb emplace_back using namespace std; typedef long long ll; const int p=998244353; int T,n,m,fa[5005],col[5005],d[5005],stk[5005],top,ct[5005][2]; int C[5005][5005],fac[5005],siz[5005],f[5005][2][5005],g[2][5005],ans; vector<int> to[5005],vec[5005]; inline void diff(int u,int v){ int S=siz[u],T=siz[v]; for(int x=0,A,B;x<2;++x){ A=B=0; for(int j=1;j<=T;++j)B=(B+f[v][x^1][j])%p; for(int i=S;i>=1;--i){ g[x][i]=(g[x][i]+((ll)A*C[S+T-i-1][T-1]+(ll)f[u][x][i]*C[S+T-i][T])%p*B)%p; A=(A+f[u][x][i])%p; } } } inline void same1(int u,int v){ int S=siz[u],T=siz[v]; for(int x=0,A,B;x<2;++x){ for(int i=1;i<S;++i){ if(!(A=f[u][x][i]))continue; B=0; for(int j=T;j>=0;--j){ B=(B+f[v][x][j])%p; g[x][i+j]=(g[x][i+j]+(ll)A*B%p*C[i+j][j]%p*C[S-i-1+T-j][T-j])%p; } } } } inline void same(int u,int v){ same1(u,v),same1(v,u); int S=siz[u],T=siz[v]; for(int x=0;x<2;++x) g[x][S+T]=(g[x][S+T]+(ll)f[u][x][S]*f[v][x][T]%p*C[S+T][T])%p; } inline void merge(int u,int v){ for(int i=1;i<=siz[u]+siz[v];++i)g[0][i]=g[1][i]=0; diff(u,v),diff(v,u),same(u,v); siz[u]+=siz[v]; for(int i=1;i<=siz[u];++i) f[u][0][i]=g[0][i],f[u][1][i]=g[1][i]; } void dfs(int u){ siz[u]=0; for(auto v:to[u]){ dfs(v); if(!siz[v])continue; if(siz[u])merge(u,v); else{ for(int i=1;i<=siz[v];++i) f[u][0][i]=f[v][0][i],f[u][1][i]=f[v][1][i]; siz[u]=siz[v]; } } if(vec[u].empty())return; int prd=1,L=vec[u].size(),c=vec[u][0],fir=0,lst=0,res=0; for(int i=0,j=0;i<L;i=j){ while(j<L && vec[u][i]==vec[u][j])++j; if(i==0)fir=j-i; if(j==L)lst=j-i; prd=(ll)prd*fac[j-i]%p; } for(int i=siz[u]+1;i<=siz[u]+L;++i)f[u][0][i]=f[u][1][i]=0; if(!siz[u])f[u][vec[u][L-1]][lst]=prd; else if(fir==L){ for(int i=siz[u];i>=1;--i){ f[u][c][i+L]=(ll)f[u][c][i]*C[i+L][i]%p*fac[L]%p; res=(res+f[u][c^1][i])%p; f[u][c^1][i]=0; } for(int i=1;i<L;++i)f[u][c][i]=0; f[u][c][L]=(ll)res*fac[L]%p; } else{ for(int i=1;i<=siz[u];++i){ res=(res+(ll)f[u][c][i]*C[i+fir][i]+f[u][c^1][i])%p; f[u][0][i]=f[u][1][i]=0; } f[u][vec[u][L-1]][lst]=(ll)res*prd%p; } siz[u]+=L; } int main(){ scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); for(int i=0;i<=n;++i){ to[i].clear(),vec[i].clear(); ct[i][0]=ct[i][1]=0; } for(int i=1;i<=n;++i) scanf("%d",fa+i),to[fa[i]].eb(i); for(int i=1;i<=m;++i)scanf("%d",col+i); for(int i=1;i<=m;++i)scanf("%d",d+i); for(int i=m,u;i>=1;--i){ top=0,u=d[i]; while(u)stk[++top]=u,u=fa[u]; for(int j=top;j>=1;--j){ if(j>1 && !ct[stk[j]][col[i]^1])continue; vec[stk[j]].eb(col[i]),++ct[stk[j]][col[i]]; break; } } C[0][0]=fac[0]=1; for(int i=1;i<=m;++i){ for(int j=1;j<=i;++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%p; C[i][0]=1; fac[i]=(ll)fac[i-1]*i%p; } dfs(1),ans=0; for(int i=1;i<=m;++i) ans=((ll)ans+f[1][0][i]+f[1][1][i])%p; printf("%d\n",ans); } return 0; }
题解总结
- 核心思想:芯片绑定节点 + 树型DP合并生成函数;
- 关键公式:阶乘(同色排列)+ 组合数(序列拼接);
- 算法流程:绑定→预处理→DFS合并→统计答案;
- 1
信息
- ID
- 6398
- 时间
- 2500ms
- 内存
- 1024MiB
- 难度
- 10
- 标签
- 递交数
- 16
- 已通过
- 1
- 上传者