1 条题解

  • 0
    @ 2025-8-24 22:49:13

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Po7ed
    **

    搬运于2025-08-24 22:49:13,当前版本为作者最后更新于2024-05-06 14:13:10,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    题目传送门

    以下设 dis(x,y)\operatorname{dis}(x,y) 表示树上 x,yx,y 两点间的距离。修改时对 uu 的周围与 uu 距离小于等于 dd 的点的点权乘 ww


    暴力不行,于是考虑打标记。

    注意到 0d400\le d\le 40,一个很自然的想法是:设 tag(x,i)tag(x,i) 表示xx 的子树内xx 距离小于等于 ii 的所有点的点权乘 tag(x,i)tag(x,i)。修改时遍历 ll 分别表示 uuuu 的父亲……uudd 级祖先,将 tag(l,ddis(u,l))tag(l,d-\operatorname{dis}(u,l)) 都乘上 ww 即可。查询时依然遍历 ll,沿途把标记累乘即可。这样 ll 最多 O(d)O(d) 个取值,总时间复杂度是 O(nd)O(nd) 的。

    显然这样会有点权被重复乘 ww,解决方案也很简单,打标记时将重复的用除法抵消。具体的,设 ll 的一个孩子 ss,使 ss 的子树内有 uu,打标记时将 tag(s,ddis(u,l)1)tag(s,d-\operatorname{dis}(u,l)-1)tag(s,ddis(u,s)2)tag(s,d-\operatorname{dis}(u,s)-2) 除以 ww 即可。

    下图给出了一个例子帮助理解:

    图中 11 号点(即 uu)将它自己和它下面的 33 个点乘了 ww22 号点也将其子树内的 55 个点乘 ww,但 11 号点由于被重复乘,通过除法被消去了一次。同理,33 号乘了 33 个点,其中 22 号点被消去了一次。44 号乘了它自己。这样每个需要乘的点都刚好被乘了一次。

    但模数可能没有逆元,这样做还是不行。

    不妨换个角度,观察到 ll 可以取到 ss,即对于 ss 的标记的操作,事实上是 tag(s,ddis(u,s))tag(s,d-\operatorname{dis}(u,s))wwtag(s,ddis(u,s)2)tag(s,d-\operatorname{dis}(u,s)-2) 除以 ww

    考虑对标记的定义稍作修改,设 tag(x,i)tag'(x,i) 表示xx 的子树内xx 距离刚好等于 ii 的所有点的点权乘 tag(x,i)tag'(x,i),则上面对 ss 的标记的操作等价于 tag(s,ddis(u,s)1)tag'(s,d-\operatorname{dis}(u,s)-1)tag(s,ddis(u,s))tag'(s,d-\operatorname{dis}(u,s)) 都乘 ww我们成功地将一乘一除转化为了两个乘。(有点类似前缀和的还原。)对应到上图的例子,就是换一个乘 ww 消:

    这样,时间复杂度依然是 O(nd)O(nd) 的,询问时还是直接跳祖先乘标记。

    代码:

    #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
    上传者