/ 7月/ 用一首诗向过去诀别
7.7 T3#
statement#
有一棵树,有点权 ,每次可以缩起来一条边,新点的点权为 ,求所有操作顺序最后得到点的点权和。
solution#
考虑组合意义,点权相当于可以选择相乘或者选择变成 ,考虑我们用若干连通块表示,如果选择相乘就不合并连通块,否则就合并连通块,那么最终大小 的连通块贡献就是 , 的连通块贡献就是 ,乘起来就是答案。
那么直接对连通块划分计数,我们需要从每个连通块内选择出一条边作为最后一条边,然后要求其他边的合并顺序在这条边之前,对于连接不同连通块的边,要求连接的两个连通块的最后一条边的顺序在这条边之前,那么我们把这些顺序要求连边,就等价于计数拓扑序个数。
注意到连出的边忽略掉方向后变成了一棵树,这个经典做法是:把向上的边容斥成向下的,然后变成若干森林再进行外向树的拓扑序计数,在本题中需要记录当前树的子树大小以及当前连通块的大小,复杂度 ,链上的时候可以把每种长度的连通块预处理一下做到 。
然后好像做不到 ,因此我们要换个做法计算拓扑序个数。
我们把每个连通块的最后一条边称作特殊边。
考虑直接设 表示只考虑子树内的边,当前连通块已经有了特殊边,且特殊边在这些边里排名为 的方案数, 表示只考虑子树内的边,当前连通块还没有特殊边,且假设特殊边在这些边里排名为 的方案数(相当于提前把特殊边的位置留出来)。
这样设状态的好处是 和 合并的时候只需要把特殊边前后分别合并就好,复杂度就是对了,而合并不同连通块的时候可以预处理一下前缀和,总的来说可以通过一些分类讨论做到 。
#include<bits/stdc++.h>
using namespace std;
const int N = 5020;
template <typename T>inline void read(T &x)
{
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c-'0');
x=(f?-x:x);
}
typedef long long LL;
int n,W[N];
const int mod = 998244353;
inline int sub(int a,int b){return a-b<0?a-b+mod:a-b;}
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int mul(int a,int b){return 1ll*a*b%mod;}
vector<int> T[N];
int C[N][N];
void init(int n)
{
for(int i=0;i<=n;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=add(C[i-1][j-1],C[i-1][j]);
}
}
int f[N][N],g[N][N],h[N],siz[N],F[N],G[N];
inline int mk(int i,int j)
{
if(i<0||j<0)return 0;
return C[i+j][i];
}
int B[N];
inline int val(int a,int b,int c,int d){return mul(mk(c,d),mk(a-c,b-d));}
void dfs(int x,int pre)
{
siz[x]=1;
for(int y:T[x])if(y^pre)
{
dfs(y,x);
siz[x]+=siz[y];
}
h[x]=W[x];
int c=0;
g[x][0]=1;
for(int y:T[x])if(y^pre)
{
int H=h[x];h[x]=0;
for(int i=0;i<=siz[y];i++)B[i]=0;
int s=0;
for(int j=siz[y]-1;j>=0;j--)s=add(s,f[y][j]),B[j]=s;
h[x]=add(h[x],mul(mul(H,mul(h[y],siz[y])),mk(c,siz[y])));
for(int i=0;i<siz[y];i++)if(f[y][i])
h[x]=add(h[x],mul(mul(H,mul(f[y][i],i+1)),mk(c,siz[y])));
for(int i=0;i<=c;i++)
{
G[i]=g[x][i];F[i]=f[x][i];
g[x][i]=f[x][i]=0;
}
for(int i=0;i<=c;i++)
{
if(G[i])
{
for(int j=0;j<=siz[y]-1;j++)//G*H->G
g[x][i+j+1]=add(g[x][i+j+1],mul(mul(G[i],mul(i+1,h[y])),val(c+1,siz[y]-1,i+1,j)));
s=0;
for(int j=1;j<=siz[y];j++)//G*F->G
{
s=add(s,B[j-1]);
g[x][i+j]=add(g[x][i+j],mul(mul(G[i],s),val(c,siz[y],i,j)));
}
for(int j=0;j<=siz[y]-1;j++)//G*F->F
{
f[x][i+j]=add(f[x][i+j],mul(mul(G[i],f[y][j]),mul(val(c,siz[y]-1,i,j),siz[y]-1-j-1+1)));
g[x][i+j]=add(g[x][i+j],mul(mul(G[i],g[y][j]),mul(val(c,siz[y],i,j),siz[y]-1-j+1)));
f[x][i+j]=add(f[x][i+j],mul(mul(G[i],g[y][j]),val(c,siz[y]-1,i,j)));
}
}
if(F[i])
{
for(int j=0;j<=siz[y]-1;j++)//F*H->F
f[x][i+j+1]=add(f[x][i+j+1],mul(mul(F[i],mul(i+1,h[y])),val(c-1+1,siz[y]-1,i+1,j)));
s=0;
for(int j=1;j<=siz[y];j++)//F*F->F
{
s=add(s,B[j-1]);
f[x][i+j]=add(f[x][i+j],mul(mul(F[i],s),val(c-1,siz[y],i,j)));
}
for(int j=0;j<=siz[y]-1;j++)//F*G->F
{
f[x][i+j]=add(f[x][i+j],mul(mul(F[i],g[y][j]),mul(val(c-1,siz[y],i,j),siz[y]-1-j+1)));
}
}
}
c+=siz[y];
}
}
int main()
{
read(n);
for(int i=1;i<=n;i++)read(W[i]);
for(int i=1;i<n;i++)
{
int x,y;
read(x);read(y);
T[x].push_back(y);
T[y].push_back(x);
}
init(n);
dfs(1,0);
int ans=h[1];
for(int i=0;i<=n;i++)ans=add(ans,f[1][i]);
cout<<ans;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】