1 条题解
-
0
自动搬运
来自洛谷,原作者为

b6e0_
搬运于
2025-08-24 22:31:10,当前版本为作者最后更新于2021-03-15 23:14:49,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
官方题解。
本文中所有未经说明的函数都与题目中形式化题面的定义相同。设 表示 到 的简单路径上所有节点的点权和( 和 也算在内), 表示 到 的简单路径上所有节点的点权积( 和 也算在内), 表示以 为根的子树节点个数,根是哪个节点见语境。
先考虑对于确定的 ,确定的 ,怎么计算 。
的定义就是一个分段函数的形式,所以考虑对于 和 的两种 分别求和。 中情况 的式子可以做出如下转换:
$$\begin{aligned}f(F(x),u)+a_x&=f(F(F(x)),u)+a_{F(x)}\\&=f(F(F(F(x))),u)+a_{F(F(x))}+a_{F(x)}+a_x\\&\cdots\\&=S(x,u)+f(F(u),u)\end{aligned} $$于是,处理出 表示 ,以 为根的子树中节点的 和就是 。这里给出 的递推式:。
对于 的情况,以 为根, 为节点 的深度,拎起一棵树,直接求即可。
当 变化时,以 为根的子树中节点的 和仍然可以用 :首先要对于所有节点 求出 和 ,然后对于 的计算,考虑在下图中,将 换成它的一个孩子 , 会发生什么变化:

观察图,原来子树外的部分是绿框,现在是蓝框,发现多的部分就是 除 以外其他孩子的子树和 ,也就是图中红色框的部分,按照原来层次传上来的贡献。于是,对于每个节点 ,求出 ,在每次准备将 换成 时,对于 的所有孩子的 按照 dfs 顺序求一个前缀 和后缀 ,转移即可。
到现在,以一次 的复杂度,求出了 ,总时间复杂度是 ,还不够优秀,考虑继续优化。因为这里是对每个节点求一个值,根据套路,考虑换根,即考虑根从 变到 时答案的变化。
此处先考虑 。注意到根 的变化,除了 和 ,影响不到你拎起来的这棵树。这棵拎起来的树只跟 , 有关,而这些东西,除了 和 ,在换根时没有变化。类似地, 的情况,只跟子树中有哪些节点有关,除了 和 ,其他的子树都没有变化!于是,用上面的计算方法,只需要特殊处理 和 时的 。
我太懒了就没有画图。处理 和 时的 需要算出一些额外的值: 表示树中所有节点到 路径上的点权和,即 ,这个值可以用换根 dp 求出;其他的值可以用类似上面的方法求出。总时间复杂度 。代码(建议复制到 IDE 里看):
#include<bits/stdc++.h> using namepace std; const int mod=998244353; vector<int>g[500010]; int n,siz[500010]; long long a[500010],ss[500010],as[500010],sp[500010],ssp[500010],uans[500010],ans[500010];//ssp为子树内sp的和,uans[x]表示当r=1时的W(r) inline int read()//快读 { char c=getchar(); int x=0; while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9'){ x=(x<<3)+(x<<1)+c-'0'; c=getchar(); } return x; } inline void write(int x)//快写 { if(!x){ putchar('0'); putchar('\n'); return; } while(x<0) x+=mod; int sta[10],tp=0; while(x){ sta[++tp]=x%10; x/=10; } while(tp) putchar(sta[tp--]+'0'); putchar('\n'); } void dfs(int x,int f)//求出siz,ss,sp,ssp { siz[x]=1; sp[x]=-1; for(int i=0;i<g[x].size();i++) if(g[x][i]!=f) { dfs(g[x][i],x); siz[x]+=siz[g[x][i]]; ss[x]=(ss[x]+ss[g[x][i]])%mod; sp[x]=max(sp[x],sp[g[x][i]]); } ss[x]=(ss[x]+siz[x]*a[x]%mod)%mod; if(sp[x]==-1) ssp[x]=sp[x]=a[x]; else ssp[x]=sp[x]=sp[x]*a[x]%mod; for(int i=0;i<g[x].size();i++) if(g[x][i]!=f) ssp[x]=(ssp[x]+ssp[g[x][i]])%mod; } void dp1(int x,int f,long long nf,long long ns)//求出uans,ans[1],as { uans[x]=(ss[x]+nf*siz[x]%mod+ns)%mod; ans[1]=(ans[1]+uans[x])%mod; if(x!=1) as[x]=(as[f]-a[f]*siz[x]+a[x]*(n-siz[x])%mod)%mod; int i,pt=0,lt=g[x].size()-2; vector<long long>pm,lm;//前缀,后缀最大值 if(x==1) lt++; pm.push_back(0); lm.push_back(0); for(i=0;i<g[x].size();i++) if(g[x][i]!=f) pm.push_back(max(pm.back(),sp[g[x][i]])); for(i=g[x].size()-1;~i;i--) if(g[x][i]!=f) lm.push_back(max(lm.back(),sp[g[x][i]])); for(i=0;i<g[x].size();i++) if(g[x][i]!=f) { dp1(g[x][i],x,max(max(nf,1ll),max(pm[pt],lm[lt]))*a[x]%mod,(ns+ssp[x]-ssp[g[x][i]]-sp[x]+max(max(nf,1ll),max(pm[pt],lm[lt]))*a[x]%mod)%mod); pt++; lt--; } } void dp2(int x,int f)//求出ans { ans[x]=(ans[f]-as[f]-uans[x]+as[x]+ssp[x]+sp[x]*(n-siz[x])%mod+as[f]-ss[x]-a[f]*siz[x]%mod)%mod; for(int i=0;i<g[x].size();i++) if(g[x][i]!=f) dp2(g[x][i],x); } int main() { int i,x,y; n=read(); for(i=1;i<=n;i++) a[i]=read()%mod;//进来要先%!否则在比较大小时会出问题 for(i=1;i<n;i++) { x=read(); y=read(); g[x].push_back(y); g[y].push_back(x); } dfs(1,0); as[1]=ss[1]; dp1(1,0,0,0); for(i=0;i<g[1].size();i++)//不想让节点1再转移一遍,所以直接对1的孩子做 dp2(g[1][i],1); for(i=1;i<=n;i++) write(ans[i]); return 0; }
- 1
信息
- ID
- 6585
- 时间
- 1200ms
- 内存
- 256MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者