P3250 [HNOI2016] 网络 (树剖 + 线段树维护堆)

题目链接: P3250 [HNOI2016] 网络

大致题意

给定一棵有 n n n个节点的树, 有 m m m次如下操作:

0 a b c 表示在 ( a , b ) (a, b) (a,b)的最短路径上增加一条重要度 c c c的边.

1 t 表示删除第 t t t次操作所增加的边

2 x 表示节点 x x x出现故障. 此时需要回答, 所有不经过 x x x节点的边中最大的重要度.

解题思路

➡️树剖 + 树差 + 整体二分做法点这里⬅️

首先我们考虑查询操作, 我们需要每次对删除节点x的连通路径上的最大边权.

但本题我们可以把边权直接等价变为点权. 因此可以把查询操作变成查询最大点权.


进而再次分析题目, 我们发现修改操作, 都是对于指定路径去增加权值, 而在查询的时候, 我们又要去排除某个点的权值. 因此我们不妨进行思维转换:

我们考虑每次对于路径 ( a , b ) (a, b) (a,b)增加权值时, 我们不去修改路径 ( a , b ) (a, b) (a,b)的权值, 转而去改变其补集的权值.

通过这种方式, 我们每个点上保存的权值, 就是不经过该点的所有权值了. 这样在查询的时候, 我们只需要对于给定点x进行查询即可.


接下来考虑这种做法是否可行.

我们考虑到由于有树链修改操作, 我们可以通过重链剖分来处理.

正常树剖每次修改操作, 是把 ( a , b ) (a, b) (a,b)路径拆分成之多 l o g n logn logn个区间, 对于每个区间进行修改.

那么如果我们需要修改路径 ( a , b ) (a, b) (a,b)的补集, 原理大致相同. 就是去修改区间的补集即可. 同样这些区间也至多只有 l o g n logn logn个, 因此复杂度不受影响.

考虑到每次区间修改, 我们需要对一个区间都增加/删除一个数值.

因此我们不难想到可以通过线段树套平衡树实现这一操作. 考虑到平衡树中不需要实现第 k k k大操作, 因此我们考虑用 m u l t i s e t multiset multiset容器代替.

特别的, 防止空间和时间复杂度爆炸, 此处需要用到线段树的标记永久化.

此时, 我们得到了一个总体时间复杂度为: O ( n l o g 3 n ) O(nlog^3n) O(nlog3n)的做法. 洛谷神机+O2一定可以带我们AC的!

特别的, 空间复杂度计算: 由于每次修改, 我们是修改一个树链的补集, 因此会修改 l o g n logn logn个区间, 每次区间修改又会影响 l o g n logn logn层. 因此总空间复杂度为: O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)


但是我们忽略了很重要的一点, multiset 本身erase掉数据后, 空间是不会释放的, 因此我们写完后交上去会 M L E MLE MLE. 因此考虑空间优化.

我们观察到查询操作本质每次只需要输出最大值即可, 因此我们可以考虑把平衡树换成大根堆.
即: 把multiset换成priority_queue.

经测试发现, 本身priority_queue也比multiset省空间.

但是又考虑到, 优先队列不支持任意删除元素, 因此考虑再维护一个删除堆, 记录需要被删除的元素.

经过一通折腾, 其实也只是优化了常数性能(包括时间与空间), 然后就可以愉快ac了!


➡️双倍经验, 强制在线版本点这里⬅️

AC代码

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10;
int n, m;
/* 树链剖分模版 */
int w[N];
vector<int> edge[N];
struct EDGE { int a, b, c; }EG[N << 1]; // 如果涉及到修改第i条边的情况, 可以这样存储

int p[N], dep[N], sz[N], son[N];
void dfs1(int x = 1, int fa = 0) {
	p[x] = fa, dep[x] = dep[fa] + 1, sz[x] = 1; // son[x] = 0;
	for (auto& to : edge[x]) {
		if (to == fa) continue;
		dfs1(to, x);
		sz[x] += sz[to];
		if (sz[to] > sz[son[x]]) son[x] = to;
	}
}
int id[N], nw[N], top[N], ind;
void dfs2(int x = 1, int tp = 1) {
	id[x] = ++ind, nw[ind] = w[x], top[x] = tp;

	if (!son[x]) return;
	dfs2(son[x], tp);

	for (auto& to : edge[x]) {
		if (to == p[x] or to == son[x]) continue;
		dfs2(to, to);
	}
}



/* 线段树模版 */
struct SEG {
	struct node {
		int l, r;
		priority_queue<int> val, del;
		int top() {
			while (!val.empty() and !del.empty() and val.top() == del.top()) val.pop(), del.pop();
			return val.empty() ? -1 : val.top();
		}

	}t[N << 2];
	void build(int l, int r, int x = 1) {
		t[x] = { l, r };
		if (l == r) return;
		int mid = l + r >> 1;
		build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
	}
	void modify(int l, int r, int c, int x = 1) {
		if (l <= t[x].l and r >= t[x].r) {
			c > 0 ? t[x].val.push(c) : t[x].del.push(-c);
			return;
		}
		int mid = t[x].l + t[x].r >> 1;
		if (l <= mid) modify(l, r, c, x << 1);
		if (r > mid) modify(l, r, c, x << 1 | 1);
	}
	int ask(int a, int x = 1) {
		if (t[x].l == t[x].r) return t[x].top();
		int mid = t[x].l + t[x].r >> 1;
		return max(t[x].top(), ask(a, x << 1 | (a > mid)));
	}
}seg;


void modify_route(int a, int b, int c) {
	vector<pair<int, int>> area;
	while (top[a] != top[b]) {
		if (dep[top[a]] < dep[top[b]]) swap(a, b);
		area.push_back({ id[top[a]], id[a] });
		a = p[top[a]];
	}
	if (id[a] > id[b]) swap(a, b);
	area.push_back({ id[a], id[b] });

	sort(area.begin(), area.end());

	int L = 1;
	// [L, l - 1], L = r + 1
	for (auto& [l, r] : area) {
		if (L <= l - 1) seg.modify(L, l - 1, c);
		L = r + 1;
	}
	if (L <= n) seg.modify(L, n, c);
}
int main()
{
	cin >> n >> m;
	rep(i, n - 1) {
		int a, b; scanf("%d %d", &a, &b);
		edge[a].push_back(b), edge[b].push_back(a);
	}
	dfs1(), dfs2();
	seg.build(1, n);

	rep(i, m) {
		int tp; scanf("%d", &tp);
		if (tp == 2) {
			int x; scanf("%d", &x);
			printf("%d\n", seg.ask(id[x]));
		}
		else if (!tp) {
			int a, b, c; scanf("%d %d %d", &a, &b, &c);
			EG[i] = { a, b, c };
			modify_route(a, b, c);
		}
		else {
			int x; scanf("%d", &x);
			const auto& [a, b, c] = EG[x];
			modify_route(a, b, -c);
		}
	}

	return 0;
}

END

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值