P4175 [CTSC2008] 网络管理 题解
前置知识:P2633 Count on a tree & P2617 Dynamic Rankings 。如果对本题一点思路都没有的话建议先做以上两题。
首先考虑如何在序列上实现本题操作,即单点修改区间 kth。如果不考虑修改的话可以直接用可持久化线段树做。那么现在的问题就是如何实现修改操作。如果暴力地做,则要对后缀中每个线段树进行修改,必 T。所以要对此进行优化。
回想一下 【模板】可持久化线段树 2。这道题我们在查询
可以发现这种形式就是查询前缀和再差分得到答案。由于树状数组在单点修改和查询前缀和上有着优秀的复杂度和常数,所以我们可以把线段树套在 BIT 里面。具体地,即每个 BIT 节点维护一个互不相交的(动态开点)线段树的根。
这种数据结构每次修改时使用类似 BIT 的方式,在
时间复杂度
核心代码如下。(为 P2617 Dynamic Rankings 代码片段。)
// 这个代码现在看来太屎山了。但我懒得重构了,能过就行。
int query(vector<int> &r1, vector<int> &r2, int k, int left, int right) {
int mid = (left + right) >> 1;
if (left == right)
return left;
int left_val = 0;
for (int it : r1) //用所有 r 对应的线段树的当前节点的左儿子减去 l-1 对应的
left_val -= (segTree[it].lson != -1 ? segTree[segTree[it].lson].val : 0);
for (int it : r2)
left_val += (segTree[it].lson != -1 ? segTree[segTree[it].lson].val : 0);
if (left_val >= k) {
for (int &it : r1) {
if (segTree[it].lson == -1) {
int new_id = new_node();
segTree[it].lson = new_id;
}
it = segTree[it].lson;
}
for (int &it : r2) {
if (segTree[it].lson == -1) {
int new_id = new_node();
segTree[it].lson = new_id;
}
it = segTree[it].lson;
}
return query(r1, r2, k, left, mid);
} else {
for (int &it : r1) {
if (segTree[it].rson == -1) {
int new_id = new_node();
segTree[it].rson = new_id;
}
it = segTree[it].rson;
}
for (int &it : r2) {
if (segTree[it].rson == -1) {
int new_id = new_node();
segTree[it].rson = new_id;
}
it = segTree[it].rson;
}
return query(r1, r2, k - left_val, mid + 1, right);
}
}
void update(int r, int id, int up, int left, int right) {
if (left == id && right == id) {
segTree[r].val += up;
return;
}
int mid = (left + right) >> 1;
if (id <= mid) {
if (segTree[r].lson == -1) {
int new_id = new_node();
segTree[r].lson = new_id;
}
update(segTree[r].lson, id, up, left, mid);
} else {
if (segTree[r].rson == -1) {
int new_id = new_node();
segTree[r].rson = new_id;
}
update(segTree[r].rson, id, up, mid + 1, right);
}
segTree[r].val = (segTree[r].lson != -1 ? segTree[segTree[r].lson].val : 0) +
(segTree[r].rson != -1 ? segTree[segTree[r].rson].val : 0);
}
inline int QUERY(int l, int r, int k) {
vector<int> r1, r2; //存 l-1 和 r 各自对应的线段树。
for (int i = l - 1; i; i -= lowbit(i))
if (root[i] != -1)
r1.push_back(root[i]);
for (int i = r; i; i -= lowbit(i))
if (root[i] != -1)
r2.push_back(root[i]);
return query(r1, r2, k, 1, kind);
}
inline void UPDATE(int id, int num, int up, int n) {
for (int i = id; i <= n; i += lowbit(i)) {
if (root[i] == -1)
root[i] = new_node();
update(root[i], num, up, 1, kind);
}
}
现在只需要把上面的东西搬到树上就行了。实际上这个和在序列上是本质相同的。树上差分即可。设
注意修改时因为修改某个节点只会影响到自身及其子树,所以要消除对无关节点的影响。
为此需要求出每个节点的 DFS 序作为树状数组中的下标,使得每个节点及其子树中节点的下标在树状数组上是连续的,便于差分来消除对无关节点的影响。因为还要求 LCA,所以可用树链剖分实现这两个需求。
时间复杂度同上,为
Code:
const int maxn = 80010;
int a[maxn], MAP[maxn << 1], kind;
struct OPERATION {
int a, b, k;
} op[maxn]; //记录操作
vector<int> edge[maxn];
int fa[maxn], dep[maxn], Size[maxn], son[maxn];
int id[maxn], top[maxn], Index;
void dfs1(int u, int FA) { //树剖。
fa[u] = FA;
dep[u] = dep[FA] + 1;
Size[u] = 1;
son[u] = 0;
for (int v : edge[u]) {
if (v == FA)
continue;
dfs1(v, u);
Size[u] += Size[v];
if (Size[v] > Size[son[u]])
son[u] = v;
}
}
void dfs2(int u, int TOP) {
id[u] = ++Index;
top[u] = TOP;
if (!son[u])
return;
dfs2(son[u], TOP);
for (int v : edge[u]) {
if (v == fa[u] || v == son[u])
continue;
dfs2(v, v);
}
}
inline int get_LCA(int a, int b) {
while (top[a] != top[b]) {
if (dep[top[a]] < dep[top[b]])
swap(a, b);
a = fa[top[a]];
}
return dep[a] < dep[b] ? a : b;
}
void disc(int MAP[], int n, int q, int len, int &m) { //离散化。
sort(MAP + 1, MAP + len + 1);
m = unique(MAP + 1, MAP + len + 1) - (MAP + 1);
for (int i = 1; i <= n; i++)
a[i] = lower_bound(MAP + 1, MAP + m + 1, a[i]) - MAP;
for (int i = 1; i <= q; i++)
if (!op[i].k)
op[i].b = lower_bound(MAP + 1, MAP + m + 1, op[i].b) - MAP;
}
inline int lowbit(int x) {
return x & (-x);
}
struct segTreeType {
struct segTreeNode {
int val;
int lson, rson;
segTreeNode() {
lson = rson = -1, val = 0;
}
};
vector<segTreeNode> segTree;
int root[maxn];
segTreeType() {
memset(root, -1, sizeof(root));
}
inline int new_node() {
segTree.push_back(segTreeNode());
return segTree.size() - 1;
}
void change_to_lson(vector<int> &r) {
for (int &it : r) {
if (segTree[it].lson == -1) {
int new_id = new_node();
segTree[it].lson = new_id;
}
it = segTree[it].lson;
}
}
template <class T, class... Args>
void change_to_lson(T &head, Args &...args) {
change_to_lson(head), change_to_lson(args...);
}
void change_to_rson(vector<int> &r) {
for (int &it : r) {
if (segTree[it].rson == -1) {
int new_id = new_node();
segTree[it].rson = new_id;
}
it = segTree[it].rson;
}
}
template <class T, class... Args>
void change_to_rson(T &head, Args &...args) {
change_to_rson(head), change_to_rson(args...);
}
int query(vector<int> &r1, vector<int> &r2, vector<int> &r3, vector<int> &r4, int k, int left, int right) {
int mid = (left + right) >> 1;
if (left == right) {
int val = 0; //要判断个数能否满足要求(>=k),<k 则无解。
for (int it : r1)
val += segTree[it].val;
for (int it : r2)
val += segTree[it].val;
for (int it : r3)
val -= segTree[it].val;
for (int it : r4)
val -= segTree[it].val;
if (k <= val)
return left;
return -1;
}
int right_val = 0;
for (int it : r1) //因为本题求第 k “大”值所以要把前一份代码中的左儿子改成右儿子。
right_val += (segTree[it].rson != -1 ? segTree[segTree[it].rson].val : 0);
for (int it : r2)
right_val += (segTree[it].rson != -1 ? segTree[segTree[it].rson].val : 0);
for (int it : r3)
right_val -= (segTree[it].rson != -1 ? segTree[segTree[it].rson].val : 0);
for (int it : r4)
right_val -= (segTree[it].rson != -1 ? segTree[segTree[it].rson].val : 0);
if (right_val >= k) {
change_to_rson(r1, r2, r3, r4);
return query(r1, r2, r3, r4, k, mid + 1, right);
} else {
change_to_lson(r1, r2, r3, r4);
return query(r1, r2, r3, r4, k - right_val, left, mid);
}
}
void update(int r, int id, int up, int left, int right) {
if (left == id && right == id) {
segTree[r].val += up;
return;
}
int mid = (left + right) >> 1;
if (id <= mid) {
if (segTree[r].lson == -1) {
int new_id = new_node();
segTree[r].lson = new_id;
}
update(segTree[r].lson, id, up, left, mid);
} else {
if (segTree[r].rson == -1) {
int new_id = new_node();
segTree[r].rson = new_id;
}
update(segTree[r].rson, id, up, mid + 1, right);
}
segTree[r].val = (segTree[r].lson != -1 ? segTree[segTree[r].lson].val : 0) +
(segTree[r].rson != -1 ? segTree[segTree[r].rson].val : 0);
}
inline int QUERY(int a, int b, int k) { //要注意用 DFS 序作为下标而不是原本编号。
vector<int> r1, r2, r3, r4;
int lca = get_LCA(a, b), lcafa = fa[lca];
for (int i = id[a]; i; i -= lowbit(i))
if (root[i] != -1)
r1.push_back(root[i]);
for (int i = id[b]; i; i -= lowbit(i))
if (root[i] != -1)
r2.push_back(root[i]);
for (int i = id[lca]; i; i -= lowbit(i))
if (root[i] != -1)
r3.push_back(root[i]);
for (int i = id[lcafa]; i; i -= lowbit(i))
if (root[i] != -1)
r4.push_back(root[i]);
return query(r1, r2, r3, r4, k, 1, kind);
}
inline void UPDATE(int id, int num, int up, int n) {
for (int i = id; i <= n; i += lowbit(i)) {
if (root[i] == -1)
root[i] = new_node();
update(root[i], num, up, 1, kind);
}
}
} segTree;
int main() {
int n, q, len = 0;
in(n, q);
for (int i = 1; i <= n; i++)
in(a[i]), MAP[++len] = a[i];
for (int i = 1; i < n; i++) {
int a, b;
in(a, b);
edge[a].push_back(b);
edge[b].push_back(a);
}
for (int i = 1; i <= q; i++) {
in(op[i].k, op[i].a, op[i].b);
if (!op[i].k)
MAP[++len] = op[i].b;
}
disc(MAP, n, q, len, kind);
dfs1(1, 0);
dfs2(1, 1);
for (int i = 1; i <= n; i++)
segTree.UPDATE(id[i], a[i], 1, n), segTree.UPDATE(id[i] + Size[i], a[i], -1, n); //要消除对无关节点影响。
for (int i = 1; i <= q; i++) {
if (op[i].k) {
int res = segTree.QUERY(op[i].a, op[i].b, op[i].k);
if (res != -1)
out(MAP[res]), enter;
else
puts("invalid request!");
} else {
segTree.UPDATE(id[op[i].a], a[op[i].a], -1, n);
segTree.UPDATE(id[op[i].a] + Size[op[i].a], a[op[i].a], 1, n);
a[op[i].a] = op[i].b;
segTree.UPDATE(id[op[i].a], a[op[i].a], 1, n);
segTree.UPDATE(id[op[i].a] + Size[op[i].a], a[op[i].a], -1, n);
}
}
}
UPD:修改部分描述使其更加严谨。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现