P9527 [JOIST 2022] 洒水器 / Sprinkler 题解

P9527 JOIST 2022 洒水器 / Sprinkler 题解

注意力惊人

思路

首先,我们注意到 Dk≤40D_k\le40Dk≤40,所以考虑暴力一些的解法。

每次直接遍历所有相邻的点肯定是不行的,但我们注意到与每个节点距离不超过 DkD_kDk 的祖先至多有 DkD_kDk 个,所以考虑对祖先进行操作。

考虑操作一个点会对哪些点产生影响。

首先肯定会对子树内的点产生影响,设 tagx,itag_{x,i}tagx,i 表示对 xxx 的子树内与 xxx 距离不超过 iii 的点的标记,那么直接对 tagx,dtag_{x,d}tagx,d 加标记即可。

然后考虑对祖先及其子树内节点的影响。

注:图片及后文的 ddd 是指当次修改的范围 DkD_kDk。

如图所示,我们只需要对 tagfx,d−(depx−depfx)tag_{fx,d-(dep_x-dep_{fx})}tagfx,d−(depx−depfx) 乘上 WWW 并对 tagy,d−(depx−depfx)−1tag_{y,d-(dep_x-dep_{fx})-1}tagy,d−(depx−depfx)−1 除去 WWW 即可。

然后每次查询就只需要暴力往上跳祖先累计答案即可。

但是此时有个严峻的问题,就是题目没有保证模数一定与 WWW 互质,那么我们就无法使用逆元了,而且常数还很大,所以我们要找到一种方法每次修改查询只用乘。

我们注意似乎每次打标记都会打很多重复的标记,考虑修改 xxx 时,我们每次修改它的祖先,每个祖先 yyy (除根节点外)都会被修改两次:一次是给以它为根的子树打上标记,一次是给它父节点去掉 x−fxx-fxx−fx 的标记。第一次修改的是将 tagy,d−(depx−depy)tag_{y,d-(dep_x-dep_y)}tagy,d−(depx−depy) 乘 WWW,第二次修改的是将 tagy,d−(depx−depfx)−1tag_{y,d-(dep_x-dep_{fx})-1}tagy,d−(depx−depfx)−1,即 tagy,d−(depx−depy)−2tag_{y,d-(dep_x-dep_y)-2}tagy,d−(depx−depy)−2 除 WWW,这似乎像一个前缀和形式,于是此时我们去掉重复修改的部分(即 0,d−(depx−depy)−20,d-(dep_x-dep_y)-20,d−(depx−depy)−2),惊人的发现:此次修改只对 yyy 的子树中与 yyy 的距离在 d−(depx−depy)−1,d−(depx−depy)d-(dep_x-dep_y)-1,d-(dep_x-dep_y)d−(depx−depy)−1,d−(depx−depy) 的节点有影响!而且对于 xxx 本身也是符合这个条件的!

所以我们将 tagx,itag_{x,i}tagx,i 的状态改为对 xxx 的子树内与 xxx 距离恰好 为 iii 的点的标记,那么每次修改时只需要把 xxx 及其祖先的 tagy,d−(depx−depy)tag_{y,d-(dep_x-dep_y)}tagy,d−(depx−depy) 和 tagy,d−(depx−depy)−1tag_{y,d-(dep_x-dep_y)-1}tagy,d−(depx−depy)−1 改一下即可。需要注意的是,对于根节点,由于它没有父节点,所以他的标记还是距离为 1,d−(depx−depy)1,d-(dep_x-dep_y)1,d−(depx−depy) 的,所以对于根节点要全部距离 ≤d−(depx−depy)\le d-(dep_x-dep_y)≤d−(depx−depy) 的 tagrttag_{rt}tagrt 都要修改一遍。

查询就暴力往上跳 ddd 个祖先 yyy,每次记录一下 tagy,depx−depytag_{y,dep_x-dep_y}tagy,depx−depy 即可。

由于 d≤40d\le 40d≤40,每次跳的祖先不超过 40,对于修改的根节点最多只有一个,最多改 ddd 个距离的标记,所以单次修改和查询的时间复杂度都为 O(d)O(d)O(d),总时间复杂度为 O(n+qd)O(n+qd)O(n+qd)。

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,mod;//节点数量及模数 
vector<int> G[200010];
ll h[200010]/*初始每个点的高度*/,tag[200010][42]/*题解中的tag数组*/; 
int fa[200010];//每个点的父节点 
void dfs(int x,int xfa){//记录每个节点的父节点 
	fa[x]=xfa;
	for(int y:G[x])if(y!=xfa){
		dfs(y,x);
	}
}
void solve(int X,int d,int val){//修改
	int x=X;
	while(d>=0&&x){
		tag[x][d]=tag[x][d]*val%mod;//修改tag[y][d-(dep[x]-dep[y])] 
		if(d-1>=0)tag[x][d-1]=tag[x][d-1]*val%mod;//修改tag[y][d-(dep[x]-dep[y])-1] 
		if(!fa[x])for(int i=0;i<=d-2;i++)tag[x][i]=tag[x][i]*val%mod;//特判根节点的情况 
		x=fa[x];//往上跳 
		d--;
	}
}
ll query(int x){//查询 
	ll ans=h[x];
	for(int i=0;i<=40&&x;i++){//往上跳40个祖先 
		ans=ans*tag[x][i]%mod;//记录当前祖先对x的标记 
		x=fa[x];//往上跳 
	}
	return ans;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>mod;
	for(int i=1,x,y;i<n;i++){
		cin>>x>>y;
		G[x].push_back(y);
		G[y].push_back(x);
	}
	dfs(1,0);
	for(int i=1;i<=n;i++)cin>>h[i];
	for(int i=1;i<=n;i++)for(int j=0;j<=40;j++)tag[i][j]=1;//记得初始化为1 
	int Q;
	cin>>Q;
	while(Q--){
		int op;
		cin>>op;
		if(op==1){
			int x,d,v;
			cin>>x>>d>>v;
			solve(x,d,v);
		}
		else{
			int x;
			cin>>x;
			cout<<query(x)<<'\n';
		}
		
	}
	return 0;
}
相关推荐
罗西的思考5 小时前
【Agentic RL】Dressage Staleness 控制深度分析
人工智能·算法·机器学习
quaer5 小时前
弗洛伊德算法--判圈法寻找环的入口O(n) O(1)
数据结构·算法
丰色木夕6 小时前
Ubuntu22.04下源码编译安装USD-Brep
c++·ubuntu·nvidia·usd-brep
高晶6 小时前
无乘法器实现LC二阶滤波器:基于移位运算的快速算法与四种精度推导
算法·设计模式·程序员
ysbfsy6 小时前
大数计算思路
算法
高晶6 小时前
主从自动分配算法:平均值乘以设备数一半的选举基准值解析
算法·设计模式·程序员
学电子她就能回来吗7 小时前
LeetCode Hot 100|普通数组专题学习笔记(零基础版)
笔记·算法·leetcode
用户老李7 小时前
反向传播计算梯度举例
算法
niyongsheng7 小时前
多智能体连续时间内路径规划:AA-SIPP(m) 的 Rust 实现与算法
算法·rust·机器人
302wanger8 小时前
DeepSeek-V4.1-Flash-1017 免费用到1024,速来
算法