做题记录 230328 // LCA

今日笑料:菌菌是在为自己辩护!


A. 暗的连锁

http://222.180.160.110:1024/contest/3470/problem/1

不难发现树上的边和附加边是两个独立的部分。

若只看所有附加边,对于某一条附加边,当它是这些附加边中的桥时,切断它所连接的两点在树上的简单路径即可达到目的。

于是我深陷于这个思路想了很久。最后 GM 告诉我们了一种新的思路:对于附加边,将 组成 它所连接的两点在树上的简单路径 的边 的权值统一加一,最后遍历每条树边:若未被标记过,则说明切掉这条边后,图已经分为两部分,答案加上附加边总数;若只被标记过一次,则贡献为标记它的那条附加边,即贡献为 1。

将边权下移至点权,则问题转化为树剖 / 树上差分。这里我用的是更难写的树剖(因为好久没写过了怕忘了)。

事实上也确实忘了。有一行判断完全写错了。紫菜!

namespace XSC062 {
#define lt (p << 1)
#define rt (lt | 1)
using namespace fastIO;
const int maxn = 1e5 + 5;
struct _ {
	int u, d;
	int l, r;
};
_ t[maxn << 2];
std::vector<int> g[maxn];
int n, m, x, y, now, res, ans;
int siz[maxn], son[maxn], fa[maxn];
int top[maxn], dfn[maxn], tab[maxn], dep[maxn];
inline void swap(int &x, int &y) {
	x ^= y ^= x ^= y;
	return;
}
inline void pushup(int p) {
	t[p].u = t[lt].u + t[rt].u;
	return;
}
inline void pushdown(int p) {
	if (t[p].d) {
		t[lt].d += t[p].d;
		t[rt].d += t[p].d;
		t[lt].u += (t[lt].r - t[lt].l + 1) * t[p].d;
		t[rt].u += (t[rt].r - t[rt].l + 1) * t[p].d;
		t[p].d = 0;
	}
	return;
}
void bld(int p, int l, int r) {
	t[p].l = l, t[p].r = r;
	if (l == r)
		return;
	int mid = (l + r) >> 1;
	bld(lt, l, mid);
	bld(rt, mid + 1, r);
	return;
}
void upd(int p, int l, int r, int v) {
	if (l <= t[p].l && t[p].r <= r) {
		t[p].d += v; 
		t[p].u += (t[p].r - t[p].l + 1) * v;
		return;
	}
	pushdown(p);
	int mid = (t[p].l + t[p].r) >> 1;
	if (l <= mid)
		upd(lt, l, r, v);
	if (r > mid)
		upd(rt, l, r, v);
	pushup(p);
	return;
}
int qry(int p, int x) {
	if (t[p].l == t[p].r)
		return t[p].u;
	pushdown(p);
	int mid = (t[p].l + t[p].r) >> 1;
	if (x <= mid)
		return qry(lt, x);
	return qry(rt, x);
}
void DFS1(int x, int f) {
	siz[x] = 1, fa[x] = f;
	for (auto i : g[x]) {
		if (i == f)
			continue;
		dep[i] = dep[x] + 1;
		DFS1(i, x);
		siz[x] += siz[i];
		if (siz[i] > siz[son[x]])
			son[x] = i;
	}
	return;
}
void DFS2(int x, int t) {
	top[x] = t;
	dfn[x] = ++now;
	tab[now] = x;
	if (son[x])
		DFS2(son[x], t);
	for (auto i : g[x]) {
		if (i == fa[x])
			continue;
		if (i != son[x])
			DFS2(i, i);
	}
	return;
}
inline void upd(int x, int y, int v) {
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]])
			swap(x, y);
		upd(1, dfn[top[x]], dfn[x], v);
		x = fa[top[x]];
	}
	if (dep[x] < dep[y])
		swap(x, y);
	upd(1, dfn[y], dfn[x], v);
	upd(1, dfn[y], dfn[y], -v);
	return;
}
inline void add(int x, int y) {
	g[x].push_back(y);
	return;
}
int main() {
	read(n), read(m);
	for (int i = 1; i < n; ++i) {
		read(x), read(y);
		add(x, y), add(y, x);
	}
	dep[1] = 1;
	DFS1(1, -1), DFS2(1, 1);
	bld(1, 1, n);
	for (int i = 1; i <= m; ++i) {
		read(x), read(y);
		upd(x, y, 1);
	}
	for (int i = 2; i <= n; ++i) {
		res = qry(1, dfn[i]);
		if (res == 0)
			ans += m;
		else if (res == 1)
			++ans;
	}
	print(ans);
	return 0;
}
} // namespace XSC062

B. 运输计划

http://222.180.160.110:1024/contest/3470/problem/2

完全不想看题面!题目是什么意思呢?给定 \(m\) 个点对和一个 \(n\) 个节点的树,在树中任选一条边使其权值为 \(0\),问 \(m\) 个点对构成的简单路径权值最大者的最小值。

看到最大的最小,考虑二分。二分一个最小值 mid,不难想到在权值大于 mid 的路径中寻找最大公共边,若最长路径减去最大公共边小于等于 mid,则返回 true

那这个寻找公共边的部分怎么实现呢?用一个 树剖 / 树上差分 就好了。因为这题卡常,所以我们写树剖(???)

还有一个点就是不难发现大于 mid 的最长路径就是整个路径集合中的最长路径(废话啊这)。


树剖卡卡校 OJ 上 3s 能过!

namespace XSC062 {
#define lt (p << 1)
#define rt (lt | 1)
using namespace fastIO;
const int maxn = 3e5 + 5;
struct _ {
	int u, d;
	int l, r;
};
struct __ {
	int v, w;
	__() {}
	__(int v1, int w1) {
		v = v1, w = w1;
	}
};
_ t[maxn << 2];
int l, r, mid, mx;
std::vector<__> g[maxn];
int n, m, x, y, w, now, res;
int u[maxn], v[maxn], q[maxn], dis[maxn];
int siz[maxn], son[maxn], fa[maxn], val[maxn];
int top[maxn], dfn[maxn], tab[maxn], dep[maxn];
inline void swap(int &x, int &y) {
	x ^= y ^= x ^= y;
	return;
}
inline int max(int x, int y) {
	return x > y ? x : y;
}
inline void pushup(int p) {
	t[p].u = t[lt].u + t[rt].u;
	return;
}
inline void pushdown(int p) {
	if (t[p].d) {
		t[lt].d += t[p].d;
		t[rt].d += t[p].d;
		t[lt].u += (t[lt].r - t[lt].l + 1) * t[p].d;
		t[rt].u += (t[rt].r - t[rt].l + 1) * t[p].d;
		t[p].d = 0;
	}
	return;
}
void bld(int p, int l, int r) {
	t[p].u = t[p].d = 0;
	t[p].l = l, t[p].r = r;
	if (l == r)
		return;
	int mid = (l + r) >> 1;
	bld(lt, l, mid);
	bld(rt, mid + 1, r);
	return;
}
void upd(int p, int l, int r, int v) {
	if (l <= t[p].l && t[p].r <= r) {
		t[p].d += v; 
		t[p].u += (t[p].r - t[p].l + 1) * v;
		return;
	}
	pushdown(p);
	int mid = (t[p].l + t[p].r) >> 1;
	if (l <= mid)
		upd(lt, l, r, v);
	if (r > mid)
		upd(rt, l, r, v);
	pushup(p);
	return;
}
int qry(int p, int x) {
	if (t[p].l == t[p].r)
		return t[p].u;
	pushdown(p);
	int mid = (t[p].l + t[p].r) >> 1;
	if (x <= mid)
		return qry(lt, x);
	return qry(rt, x);
}
void DFS1(int x, int f) {
	siz[x] = 1, fa[x] = f;
	for (auto i : g[x]) {
		if (i.v == f)
			continue;
		dep[i.v] = dep[x] + 1;
		dis[i.v] = dis[x] + i.w;
		val[i.v] = i.w;
		DFS1(i.v, x);
		siz[x] += siz[i.v];
		if (siz[i.v] > siz[son[x]])
			son[x] = i.v;
	}
	return;
}
void DFS2(int x, int t) {
	top[x] = t;
	dfn[x] = ++now;
	tab[now] = x;
	if (son[x])
		DFS2(son[x], t);
	for (auto i : g[x]) {
		if (i.v == fa[x])
			continue;
		if (i.v != son[x])
			DFS2(i.v, i.v);
	}
	return;
}
inline void upd(int x, int y, int v) {
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]])
			swap(x, y);
		upd(1, dfn[top[x]], dfn[x], v);
		x = fa[top[x]];
	}
	if (dep[x] < dep[y])
		swap(x, y);
	upd(1, dfn[y], dfn[x], v);
	upd(1, dfn[y], dfn[y], -v);
	return;
}
inline int LCA(int x, int y) {
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]])
			swap(x, y);
		x = fa[top[x]];
	}
	if (dep[x] < dep[y])
		swap(x, y);
	return y;
}
inline void add(int x, int y, int w) {
	g[x].push_back(__(y, w));
	return;
}
inline bool check(int x) {
	bld(1, 1, n);
	int cnt = 0, t = -1;
	for (int i = 1; i <= m; ++i) {
		if (q[i] > x) {
			++cnt;
			upd(u[i], v[i], 1);
		}
	}
	for (int i = 2; i <= n; ++i) {
		if (qry(1, dfn[i]) == cnt)
			t = max(t, val[i]);
	}
	return q[mx] - t <= x;
}
int main() {
	read(n), read(m);
	for (int i = 1; i < n; ++i) {
		read(x), read(y), read(w);
		add(x, y, w), add(y, x, w);
		r += w;
	}
	dep[1] = 1;
	DFS1(1, -1), DFS2(1, 1);
	bld(1, 1, n);
	for (int i = 1; i <= m; ++i) {
		read(u[i]), read(v[i]);
		q[i] = dis[u[i]] + dis[v[i]] -
					2 * dis[LCA(u[i], v[i])];
		if (q[i] > q[mx])
			mx = i;
	}
	while (l <= r) {
		mid = (l + r) >> 1;
		if (check(mid)) {
			res = mid;
			r = mid - 1;
		}
		else l = mid + 1;
	}
	print(res);
	return 0;
}
} // namespace XSC062

可惜洛谷上是 2s,\(\mathcal O(n\log^2n \log V)\) 过不了啊,但是 \(\mathcal O(n\log n\log V)\) 的树上差分就完全没有问题。恨啊!

namespace XSC062 {
using namespace fastIO;
const int maxn = 3e5 + 5;
const int LEN = (1 << 20);
struct __ {
	int v, w;
	__() {}
	__(int v1, int w1) {
		v = v1, w = w1;
	}
};
int cnt[maxn];
int l, r, mid, mx;
int n, m, x, y, w, res;
std::vector<__> g[maxn];
int top[maxn], dep[maxn];
int u[maxn], v[maxn], q[maxn], dis[maxn];
int siz[maxn], son[maxn], fa[maxn], val[maxn];
inline void swap(int &x, int &y) {
	x ^= y ^= x ^= y;
	return;
}
inline int max(int x, int y) {
	return x > y ? x : y;
}
void DFS1(int x, int f) {
	siz[x] = 1, fa[x] = f;
	for (auto i : g[x]) {
		if (i.v == f)
			continue;
		dep[i.v] = dep[x] + 1;
		dis[i.v] = dis[x] + i.w;
		val[i.v] = i.w;
		DFS1(i.v, x);
		siz[x] += siz[i.v];
		if (siz[i.v] > siz[son[x]])
			son[x] = i.v;
	}
	return;
}
void DFS2(int x, int t) {
	top[x] = t;
	if (son[x])
		DFS2(son[x], t);
	for (auto i : g[x]) {
		if (i.v == fa[x])
			continue;
		if (i.v != son[x])
			DFS2(i.v, i.v);
	}
	return;
}
inline void DFS3(int x) {
	for (auto i : g[x]) {
		if (i.v == fa[x])
			continue;
		DFS3(i.v);
		cnt[x] += cnt[i.v];
	}
	return;
}
inline int LCA(int x, int y) {
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]])
			swap(x, y);
		x = fa[top[x]];
	}
	if (dep[x] < dep[y])
		swap(x, y);
	return y;
}
inline void add(int x, int y, int w) {
	g[x].push_back(__(y, w));
	return;
}
inline bool check(int x) {
	memset(cnt, 0, (n + 1) * sizeof (int));
	int tot = 0, t = -1;
	for (int i = 1; i <= m; ++i) {
		if (q[i] > x) {
			++tot;
			++cnt[u[i]], ++cnt[v[i]];
			cnt[LCA(u[i], v[i])] -= 2;
		}
	}
	DFS3(1);
	for (int i = 2; i <= n; ++i) {
		if (cnt[i] == tot)
			t = max(t, val[i]);
	}
	return q[mx] - t <= x;
}
int main() {
	read(n), read(m);
	for (int i = 1; i < n; ++i) {
		read(x), read(y), read(w);
		add(x, y, w), add(y, x, w);
		r += w;
	}
	dep[1] = 1;
	DFS1(1, -1), DFS2(1, 1);
	for (int i = 1; i <= m; ++i) {
		read(u[i]), read(v[i]);
		q[i] = dis[u[i]] + dis[v[i]] -
					2 * dis[LCA(u[i], v[i])];
		if (q[i] > q[mx])
			mx = i;
	}
	while (l <= r) {
		mid = (l + r) >> 1;
		if (check(mid)) {
			res = mid;
			r = mid - 1;
		}
		else l = mid + 1;
	}
	print(res);
	return 0;
}
} // namespace XSC062

树刨当然是极好的!假的。


C. 天天爱跑步

http://222.180.160.110:1024/contest/3470/problem/3

好难好难好难!看不懂!

posted @ 2023-03-28 20:43  XSC062  阅读(27)  评论(0编辑  收藏  举报