题目链接: 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;
}