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

Po7ed
**搬运于
2025-08-24 22:49:13,当前版本为作者最后更新于2024-05-06 14:13:10,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
以下设 表示树上 两点间的距离。修改时对 的周围与 距离小于等于 的点的点权乘 。
暴力不行,于是考虑打标记。
注意到 ,一个很自然的想法是:设 表示将 的子树内与 距离小于等于 的所有点的点权乘 。修改时遍历 分别表示 、 的父亲…… 的 级祖先,将 都乘上 即可。查询时依然遍历 ,沿途把标记累乘即可。这样 最多 个取值,总时间复杂度是 的。
显然这样会有点权被重复乘 ,解决方案也很简单,打标记时将重复的用除法抵消。具体的,设 的一个孩子 ,使 的子树内有 ,打标记时将 即 除以 即可。
下图给出了一个例子帮助理解:

图中 号点(即 )将它自己和它下面的 个点乘了 , 号点也将其子树内的 个点乘 ,但 号点由于被重复乘,通过除法被消去了一次。同理, 号乘了 个点,其中 号点被消去了一次。 号乘了它自己。这样每个需要乘的点都刚好被乘了一次。
但模数可能没有逆元,这样做还是不行。
不妨换个角度,观察到 可以取到 ,即对于 的标记的操作,事实上是 乘 , 除以 。
考虑对标记的定义稍作修改,设 表示将 的子树内与 距离刚好等于 的所有点的点权乘 ,则上面对 的标记的操作等价于 和 都乘 。我们成功地将一乘一除转化为了两个乘。(有点类似前缀和的还原。)对应到上图的例子,就是换一个乘 消:

这样,时间复杂度依然是 的,询问时还是直接跳祖先乘标记。
代码:
#include <iostream> #include <vector> const int N=214514,D=100; int n,m,mod; int a[N]; int tag[N][D],fa[N]; std::vector<int> e[N]; void dfs(int u,int f=0) { fa[u]=f; for(int v:e[u])if(v!=f)dfs(v,u); } int main() { scanf("%d %d",&n,&mod); int u,v; for(int i=1;i<n;i++) { scanf("%d %d",&u,&v); e[u].push_back(v); e[v].push_back(u); } dfs(1); for(int i=1;i<=n;i++)scanf("%d",a+i); for(int i=1;i<=n;i++)for(int j=0;j<=40;j++)tag[i][j]=1; scanf("%d",&m); int o,x,d,w; while(m--) { scanf("%d %d",&o,&x); if(o==1) { scanf("%d %d",&d,&w); while(d>=0&&x) { tag[x][d]=1ll*tag[x][d]*w%mod; if(d>=1)tag[x][d-1]=1ll*tag[x][d-1]*w%mod; if(x==1) // 根节点要特判,因为根节点没有祖先抵消 { for(int i=0;i<=d-2;i++)tag[1][i]=1ll*tag[1][i]*w%mod; break; } x=fa[x]; d--; } } else { int ans=a[x]; for(d=0;d<=40;d++) // 跳父亲累乘 tag { if(!x)break; ans=1ll*ans*tag[x][d]%mod; x=fa[x]; } printf("%d\n",ans); } } return 0; }
- 1
信息
- ID
- 9073
- 时间
- 4000ms
- 内存
- 1024MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者