【2027算法进阶班】NOIP训练赛#17

B. T2-烟花(firework)

两个点缩成一个点 , 想象 \(u\) , \(v\) 背对背
,只考虑面对的那一边
\(f[u][i][j]\) 表示在以 \(u\) 为根的子树 ,前 \(i\) 个分支共选了 \(j\) 个分支,转移方程如下

\[f[u][i][j]=mod(f[u][i-1][j]+f[u][i-1][j-1]*siz[i]) \]

在 dfs 中枚举儿子时相当于枚举了 \(i\) ,倒序更新省掉一维

不考虑父亲节点 跑一边 dfs 更新出来 \(f\)

对于 \(u ,v\) 先取消 \(f_u\) 中 \(v\) 的贡献

然后 \(m^2\) 预处理 \(f_u\) 和 \(f_v\) 的融合版

最后算答案的时候加上 \(u\) 父亲方向的贡献

Code
#include<bits/stdc++.h>
#define ll long long
#define ac read()
using namespace std;
const int N=300005;
const int M=55;
const int Mod=998244353;
inline ll read() {
  ll x=0,f=1;
  int c=getchar();
  while(!isdigit(c)) {
  	f=(c=='-')?-1:1;
  	c=getchar();
  }
  while(isdigit(c)) {
  	x=(x<<3)+(x<<1)+(c^48);
  	c=getchar();
  }
  return x*f;
}
struct node {
  int nex,to;
} e[N<<1];
int head[N],tot;
inline void add(int u,int v) {
  e[++tot].nex=head[u];
  e[tot].to=v;
  head[u]=tot;
}
int n,m;
ll f[N][M];
int U[N],V[N];
int FA[N],siz[N];
inline ll MOD(ll a) {
  return (a+Mod)%Mod;
}
void dfs(int u,int fa) {
  FA[u]=fa;
  siz[u]=1;
  f[u][0]=1;
  for(int i=head[u]; i!=-1; i=e[i].nex) {
  	int to=e[i].to;
  	if(to==fa) continue;
  	dfs(to,u);
  	siz[u]+=siz[to];
  	for(int i=m; i>=1; --i) {
  		f[u][i]=MOD(f[u][i]+f[u][i-1]*siz[to]);
  	}
  }
}
ll g1[M],g2[M];
int main() {
  memset(head,-1,sizeof head);
  n=ac,m=ac;
  for(int i=1; i<n; ++i) {
  	int u=ac,v=ac;
  	add(u,v);
  	add(v,u);
  	U[i]=u,V[i]=v;
  }
  dfs(1,0);
  for(int i=1; i<n; ++i) {
  	int u=U[i],v=V[i];
  	if(FA[u]==v) swap(u,v);
  	g1[0]=1;
  	g2[0]=0;
  	for(int j=1; j<=m; ++j) {
  		g1[j]=MOD(f[u][j]-g1[j-1]*siz[v]);
  	}
  	for(int j=0; j<=m; ++j) {
  		g2[j]=0;
  		for(int k=0; k<=j; ++k) {
  			g2[j]=MOD(g2[j]+g1[k]*f[v][j-k]);
  		}
  	}
  	int Fa=n-siz[u],ans=1;
  	for(int j=1; j<=m; ++j) {
  		ans=MOD(ans+g2[j]+g2[j-1]*Fa);
  	}
  	cout<<ans<<"\n";
  }
  return 0;
}
posted @ 2026-11-13 20:56  Lonely_Sun  阅读(4)  评论(0)    收藏  举报