天坑树链剖分

树链剖分要
两次dfs,划分树链,之后写个线段树进行区间操作
线段树真tm天坑
注意点:
下放懒标记的同时,把子区间的值更新
updata函数里面,更新了当前区间的话,函数结尾要更新当前区间
不要把rk填成id
可以不用写链式前向星的
别把懒标记从叶子节点下放

struct edge
{
	int to;
	ll va;
};

int main()
{//将使用链式前向星的写法进行树链剖分
	int n = q_;
	int q = q_;
	int rt = q_;
	ll P = q_;
	vector<edge>ed((n + 10) << 1);
	vector<int>head(n + 1, -1), nxt((n + 10) << 1, -1);
	vector<ll>f(n + 1), d(n + 1), sz(n + 1), son(n + 1), rk(n + 1), top(n + 1), id(n + 1), tr((n + 1) << 2, 0);
	vector<ll>w(n + 1, 0), lz((n + 1) << 2, 0);//懒标记
	//dfs1 先找到父节点和子树大小,还有要找到重儿子,还有深度
	int pocnt = 0;//树的节点数,用于链式前向星
	int cnt = 0;//重构树上节点编号
	auto add = [&](int x, int y)->void
		{
			ed[pocnt] = { y,0 };
			nxt[pocnt] = head[x];
			head[x] = pocnt++;
			
			ed[pocnt] = { x,0 };
			nxt[pocnt] = head[y];
			head[y] = pocnt++;
		};

	ffp(i, 1, n) { w[i] = q_; }
	ffp(i, 2, n)
	{
		int u = q_;
		int v = q_;
		add(u, v);
	}
	auto dfs1=[&](auto & dfs1, int now, int fa,int dep) -> void
		{
			sz[now] = 1;
			d[now] = dep;
			for (int i = head[now]; i != -1; i = nxt[i])
			{
				auto [to, va] = ed[i];
				if (to == fa) { continue; }

				dfs1(dfs1, to, now, dep + 1);
				f[to] = now;
				sz[now] += sz[to];
				if (sz[to] > sz[son[now]])
				{
					son[now] = to;
				}
			}
		};
	dfs1(dfs1, rt, 0, 1);
	//dfs2 ,维护dfn序,链接重链,处理出top,id,rk
	auto dfs2 = [&](auto& dfs2, int now, int t)->void
		{						//当前节点,重链顶端
			top[now] = t;
			id[now] = ++cnt;//dfn序列 每个点的新编号
			rk[cnt] = now;//排名为cnt 的点是
			if (!son[now])//如果是叶子节点
			{
				return;
			}
			dfs2(dfs2, son[now], t);
			//优先选择重儿子来保证dfn序的连续性

			for (int i = head[now]; i != -1; i = nxt[i])
			{
				int v = ed[i].to;
				if (v != son[now] && v != f[now])
				{
					dfs2(dfs2, v, v);//处于下一条轻链的顶端
				}
			}
		};
	dfs2(dfs2, rt, rt);
	//要建一棵线段树
	auto pushlazy = [&](int now)->void//下放标记
		{
			lz[now * 2] +=lz[now];
			lz[now * 2 + 1] += lz[now];
			lz[now * 2] %= P;
			lz[now * 2 + 1] %= P;
		};//给标记的同时已经把值改变了,不用再改变了
	auto query = [&](auto& query, int now, int l, int r, int pl, int pr)->ll
		{
			if (pl<=l && r<=pr)//到需要的节点了
			{
				return tr[now];
			}
			if (lz[now])
			{
				pushlazy(now);
				tr[now * 2] += (((r + l) >> 1) - l + 1) * lz[now];
				tr[now * 2 + 1] += (r - (r + l) / 2) * lz[now];
				tr[now * 2] %= P;
				tr[now * 2 + 1] %= P;//lazy是下放标记,在有lazy标记的时候,值已经被更新了
				lz[now] = 0;
			}
			int med = (l + r) >> 1;
			ll temp = 0;
			if (pl <= med)temp+=query(query, now * 2, l, med, pl, pr);
			if (pr > med)temp+=query(query, now * 2 + 1, med + 1, r, pl, pr);
			return temp;
		};
	auto updata = [&](auto& updata, int now, int l, int r, int pl, int pr, ll va)->void
		{
			if (pl <= l && r <= pr)//到需要的节点了
			{
				lz[now] += va;
				tr[now] += (r - l + 1) * va;
				return;
			}
			if (lz[now])
			{
				pushlazy(now);
				tr[now * 2] += (((r + l) >> 1) - l + 1) * lz[now];
				tr[now * 2 + 1] += (r - (r + l) / 2) * lz[now];
				tr[now * 2] %= P;
				tr[now * 2 + 1] %= P;//lazy是下放标记,在有lazy标记的时候,值已经被更新了
				lz[now] = 0;
			}
			int med = (l + r) >> 1;
			if (pl <= med)updata(updata, now * 2, l, med, pl, pr, va);
			if (pr > med)updata(updata, now * 2 + 1, med + 1, r, pl, pr,va);
			tr[now] = tr[now * 2] + tr[now * 2 + 1];
			tr[now] %= P;
		};
	auto bud = [&](auto bud,int now,int l,int r,int p,int va)->void
		{
			if (l == r)
			{
				tr[now] = va;
				return;
			}
			int med = (l + r) >> 1;
			if (p <= med) { bud(bud, now * 2, l, med, p, va); }
			else { bud(bud, now * 2 + 1, med + 1, r, p, va); }
			tr[now] = (tr[now * 2] + tr[now * 2 + 1])%MOD;
			return;
		};

	ffp(i, 1, n)
	{
		bud(bud, 1, 1, n, i, w[rk[i]]);
	}

	//由于第二次dfs的过程中,因为顺序是先重再轻,所以每一条重链的新编号是连续的
	//因为是dfs,所以每一个子树的新编号也是连续的
	//所以查询两点之间的点权和,所查询的所有点,当前点到当前重链顶部的点在id编号上都是连续的区间
	//于是可以考虑用线段树维护一段区间中的值,时间复杂度logn^2
	auto op1 = [&](int x, int y)->ll // 操作1,询问两点之间简单路径的点权和
		{
			ll ans = 0;
			while (top[x] != top[y])//如果两个点不在同一条链上
			{
				if (d[top[x]] < d[top[y]]) { swap(x, y); }//把x改为所在链链顶最深的那个点
				ll res = query(query, 1, 1, n, id[top[x]], id[x]);//在线段树上查找这一段区间之和
				ans += res;
				ans %= P;
				x = f[top[x]];
			}//直到两个点处于同一条链上

			if (d[x] < d[y]) { swap(x, y); }
			ll res = query(query, 1, 1, n, id[y], id[x]);
			ans += res;
			return ans % P;
		};
	auto op2 = [&](int x)->ll//查询该点及子树的点权和
		{
			ll ans = 0;
			ans = query(query, 1, 1, n, id[x], id[x] + sz[x] - 1);
			return ans%P;
		};
	//区间修改跟区间查询一样,但是要把query修改成updata
	auto op3 = [&](int x, int y,int va)->void
		{
			while (top[x] != top[y])
			{
				if (d[top[x]] < d[top[y]]) { swap(x, y); }
				updata(updata,1,1,n,id[top[x]],id[x],va);
				x = f[top[x]];
			}
			if (d[x] < d[y]) { swap(x, y); }
			updata(updata, 1, 1, n, id[y], id[x], va);
		};
	auto op4 = [&](int x, int va)->void
		{
			updata(updata, 1, 1, n, id[x], id[x] + sz[x] - 1, va);
		};

	ffp(i, 1, q)
	{
		int op = q_;
		if (op == 1)
		{
			int x = q_;
			int y = q_;
			int z = q_;
			op3(x, y, z);
		}
		else if (op == 2)
		{
			int x = q_;
			int y = q_;
			cout<< op1(x, y) << endl;
		}
		else if (op == 3)
		{
			int x = q_;
			int z = q_;
			op4(x, z);
		}
		else if (op == 4)
		{
			int x = q_;
			cout << op2(x) << endl;
		}
	}


	return 0;
}
posted @ 2025-08-02 15:23  粉紫系超人气月兔铃仙  阅读(12)  评论(0)    收藏  举报