Loading

Solution to P6845 [CEOI2019] Dynamic Diameter

Statement

给你一棵有 \(n\) 个结点的树,边带权。

\(q\) 次操作,每次修改一个边权,修改完后求树的直径。

保证 \(2\le n\le 10^5\)\(1\le q\le 10^5\)\(1\le w\le 2\times 10^{13}\)\(1\le a_i,b_i\le n\)\(0\le c_i,e_j<w\)\(0\le d_j<n-1\)

本题强制在线

Solution

前置知识:欧拉序,线段树。

对一棵树我们定义 \(d_u\) 表示结点 \(u\) 到根节点的距离。

考虑直径的定义即

\[\max{\{d_u + d_v - 2 \cdot d_{\mathrm{lca}(u, v)}\}}, u \in T, v \in T \]

每次修改一条边的边权 \(w \gets w'\),必定影响这条边所连子树上的点的信息,效果是 \(d_u \gets d_u + w' - w\)

因此考虑将 \(d_i\) 拍到欧拉序上维护。

由欧拉序的性质,发现 \(\mathrm{lca}(u, v)\) 至少会在 \(u, v\) 之间出现一次,且 \(\mathrm{lca}(u, v)\) 对应的 \(d\)\(u, v\) 所在区间最小的。

所以设 \(s\) 为欧拉序 \(e\) 的长度,令序列 \(a_i=d_{e_i}\),答案就是

\[\max_{l=1}^{s} \max_{r=l}^{s}{\{a_l + a_r - 2 \cdot \min_{i=l}^{r} {a_i}\}} \]

考虑怎么维护这个式子,就叫它直径 \(d\) 吧。

想象一下我们有一个区间 \(\mathbf{x} = [l, r)\),它有儿子们 \(\mathbf{ls} = [l, m)\)\(\mathbf{rs} = [m, r)\)。分类一下:

  • \(a_l\)\(\min_{i=l}^{r} {a_i}\) 出现在 \([l, m)\)\(a_r\) 出现在 \([m, r)\)
  • \(a_l\) 出现在 \([l, m)\)\(a_r\)\(\min_{i=l}^{r} {a_i}\) 出现在 \([m, r)\)

那我们对每一个区间设两个式子

\[\mathrm{poly}(l) = \max_{i=l}^{r} \{a_i - 2 \cdot \min_{j=i}^{r} a_j\} \]

\[\mathrm{poly}(r) = \max_{i=r}^{l} \{a_i - 2 \cdot \min_{j=i}^{l} a_j\} \]

含义分别是某个区间,选了一个 \(a_i\) 后在 \(a_i\) 的右边(\(\mathrm{poly}(l)\))或左边(\(\mathrm{poly}(r)\))选择最小值,整个式子的最大值。有了这个东西我们就能够维护 \(d\)

\[d_{\mathbf{x}} = \max\begin{cases} d_{\mathbf{ls}}\\ d_{\mathbf{rs}}\\ \mathrm{poly}(l)_{\mathbf{ls}} + \mathrm{max}_{\mathbf{rs}}\\ \mathrm{max}_{\mathbf{ls}} + \mathrm{poly}(r)_{\mathrm{rs}} \end{cases} \]

怎么维护 \(\mathrm{poly}(l)\)\(\mathrm{poly}(r)\)

不难看出我们在左儿子选一个最大值,在右儿子选一个最小值,加上原来两个区间的值就能维护 \(\mathrm{poly}(l)\) 了,对于 \(\mathrm{poly}(r)\) 同理。

\[\mathrm{poly}(l)_{\mathbf{x}} = \max\begin{cases} \mathrm{poly}(l)_{\mathbf{ls}}\\ \mathrm{poly}(l)_{\mathbf{rs}}\\ \mathrm{max}_{\mathbf{ls}} + \mathrm{min}_{\mathbf{rs}} \end{cases} \]

\[\mathrm{poly}(r)_{\mathbf{x}} = \max\begin{cases} \mathrm{poly}(r)_{\mathbf{ls}}\\ \mathrm{poly}(r)_{\mathbf{rs}}\\ \mathrm{min}_{\mathbf{ls}} + \mathrm{max}_{\mathbf{rs}} \end{cases} \]

最后维护 \(\max\)\(\min\),我们就能维护出一个 \(d\) 来。

修改呢?在欧拉序上做做文章,我们把当前要修改的边,所连的较深结点的子树进行一个区间修改即可。

设边权变化量为 \(k\) 的话,这里 \(\max \gets \max + \space k\)\(\min \gets \min + \space k\)\(\mathrm{poly}(l) \gets \mathrm{poly}(l) - \space k\)\(\mathrm{poly}(r) \gets \mathrm{poly}(r) - \space k\)(加上 \(k\) 减去 \(2k\)),\(d\) 没有变化(不涉及对子树内部边权的修改)。

做完了!时间复杂度 \(\mathcal{O}(q \log n)\)

Code

代码中 dmt 代表 \(d\)lpoly 代表 \(\mathrm{poly}(l)\)rpoly 同理。

代码中大多数的下标是从零开始的。

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>

const int _Size = 16;

class io_r {
private:
	static char buf[];
	static int p;
	FILE* f;

public:
	inline void flush() { fread(buf, sizeof(char), 1 << _Size, f), p = 0; }
	inline io_r& operator>>(char& ch) {
		if (p == (1 << _Size)) flush();
		ch = buf[p++];
		return *this;
	}
	template <typename T>
	inline io_r& operator>>(T& x) {
		x = 0;
		short w = 1;
		char ch = 0;
		do w = ch == '-' ? -1 : w, *this >> ch;
		while (ch < '0' || ch > '9');
		do x = (x << 3) + (x << 1) + ch - '0', *this >> ch;
		while ('0' <= ch && ch <= '9');
		x *= w;
		return *this;
	}
	io_r() : f(stdin) {}
	io_r(const char* Filename, const char* Mode = "r") : f(fopen(Filename, Mode)) {
		if (!f) std::perror("File Opening Failed.");
	}
	io_r(FILE* f) : f(f) {}
};

class io_w {
private:
	static char buf[];
	static int p;
	FILE* f;

public:
	inline void flush() { fwrite(buf, p, 1, f), p = 0; }
	inline io_w& operator<<(const char c) {
		if (p == (1 << _Size)) flush();
		buf[p++] = c;
		return *this;
	}
	inline io_w& operator<<(const char* c) {
		int len = strlen(c);
		for (int i = 0; i < len; ++i) *this << c[i];
		return *this;
	}
	template <typename T>
	inline io_w& operator<<(T x) {
		if (x < 0) *this << '-', x = -x;
		static int s[50], d = 0;
		do s[++d] = x % 10, x /= 10;
		while (x);
		do *this << (char)(s[d--] + '0');
		while (d);
		return *this;
	}
	~io_w() { flush(); }
	io_w() : f(stdout) {}
	io_w(const char* Filename, const char* Mode = "w") : f(fopen(Filename, Mode)) {}
	io_w(FILE* f) : f(f) {}
};

char io_r::buf[1 << _Size];
int io_r::p = 0;
char io_w::buf[1 << _Size];
int io_w::p = 0;

// quick_io above

using i64 = long long;

struct Node {
public:
	i64 dmt, min, max, lpoly, rpoly, tag;
	Node(const i64 dmt = i64(), const i64 min = i64(), const i64 max = i64(),
		 const i64 lpoly = i64(), const i64 rpoly = i64(), const i64 tag = i64())
		: dmt(dmt), min(min), max(max), lpoly(lpoly), rpoly(rpoly), tag(tag) {}
	inline Node& operator<<(const i64 _tag) {
		return *this = Node(dmt, min + _tag, max + _tag, lpoly - _tag, rpoly - _tag, tag + _tag);
	}
};
inline Node operator+(const Node ls, const Node rs) {
	return Node(std::max({ls.dmt, rs.dmt, ls.lpoly + rs.max, ls.max + rs.rpoly}),
				std::min(ls.min, rs.min), std::max(ls.max, rs.max),
				std::max({ls.lpoly, rs.lpoly, ls.max - 2ll * rs.min}),
				std::max({ls.rpoly, rs.rpoly, rs.max - 2ll * ls.min}), i64());
}

class SegmentTree {
private:
	std::vector<i64> a;
	std::vector<Node> p;
	size_t n;
	inline size_t ls(const size_t x) { return (x << 1) + 1; }
	inline size_t rs(const size_t x) { return (x << 1) + 2; }
	inline void push_down(const size_t x) {
		p[ls(x)] << p[x].tag;
		p[rs(x)] << p[x].tag;
		p[x].tag = i64();
	}
	void build(const size_t l, const size_t r, const size_t x = 0) {
		if (r - l == 1) return p[x] << a[l], void();
		size_t mid = ((l + r) >> 1);
		build(l, mid, ls(x));
		build(mid, r, rs(x));
		p[x] = p[ls(x)] + p[rs(x)];
	}

public:
	void modify(const size_t ql, const size_t qr, const i64 k, const size_t l, const size_t r,
				const size_t x) {
		if (ql <= l && r <= qr) return p[x] << k, void();
		push_down(x);
		size_t mid = ((l + r) >> 1);
		if (ql < mid) modify(ql, qr, k, l, mid, ls(x));
		if (qr > mid) modify(ql, qr, k, mid, r, rs(x));
		p[x] = p[ls(x)] + p[rs(x)];
	}
	Node query() { return p.front(); }
	size_t size() { return a.size(); }

	SegmentTree(std::vector<i64> a) : a(a), p(a.size() << 2), n(a.size()) { build(0, n, 0); }
};

// segment tree

class Tree {
private:
	struct Edge {
		int to, nxt;
		i64 weight;
		Edge(const int to, const int nxt, const i64 weight) : to(to), nxt(nxt), weight(weight) {}
	};
	struct Edge_t {
		int father, son;
		i64 weight;
		Edge_t(const int father = int(), const int son = int(), const i64 weight = i64())
			: father(father), son(son), weight(weight) {}
	};

	int n;

	std::vector<Edge> ed;
	std::vector<int> head;
	std::vector<i64> dep;

public:
	inline void add_edge(int u, int v, i64 w) {
		ed.push_back(Edge(v, head[u], w));
		head[u] = (int)ed.size() - 1;
	}

	std::vector<Edge_t> ed_t;
	std::vector<int> euler, lbound, rbound;

	void dfs_build_euler_order(int x, int f, i64 d) {
		lbound[x] = euler.size();
		euler.push_back(x);
		dep[x] = d;
		for (int i = head[x]; ~i; i = ed[i].nxt) {
			int to = ed[i].to;
			i64 weight = ed[i].weight;
			if (to == f) continue;

			Edge_t& cur = ed_t[i >> 1];
			cur.father = x;
			cur.son = to;
			cur.weight = weight;

			dfs_build_euler_order(to, x, d + weight);
			euler.push_back(x);
		}
		rbound[x] = euler.size();
	}

	std::vector<i64> generate_dep_by_euler_order() {
		std::vector<i64> dep_t;
		for (int i : euler) {
			dep_t.push_back(dep[i]);
		}
		dep_t.pop_back();
		return dep_t;
	}

	Tree(int n) : n(n), head(n + 1, -1), dep(n + 1), ed_t(n - 1), lbound(n + 1), rbound(n + 1) {}
};

// euler order

io_r qin;
io_w qout;

int main() {
	int n, q;
	i64 w;
	qin >> n >> q >> w;

	Tree T(n);

	for (int i = 1; i < n; ++i) {
		int u, v;
		i64 w;
		qin >> u >> v >> w;
		T.add_edge(u, v, w);
		T.add_edge(v, u, w);
	}

	T.dfs_build_euler_order(1, 0, i64());

	SegmentTree S(T.generate_dep_by_euler_order());

	i64 last_ans = 0;
	while (q--) {
		int d;
		i64 e;
		qin >> d >> e;

		d = (d + last_ans) % (n - 1);
		e = (e + last_ans) % w;

		auto& cur = T.ed_t[d];
		S.modify(T.lbound[cur.son], T.rbound[cur.son], e - cur.weight, 0, S.size(), 0);
		qout << (last_ans = S.query().dmt) << '\n';
		cur.weight = e;
	}
	return 0;
}

posted @ 2023-07-20 20:53  escap1st  阅读(33)  评论(0)    收藏  举报