P4175 [CTSC2008] 网络管理 题解
前置知识:P2633 Count on a tree & P2617 Dynamic Rankings 。如果对本题一点思路都没有的话建议先做以上两题。
首先考虑如何在序列上实现本题操作,即单点修改区间 kth。如果不考虑修改的话可以直接用可持久化线段树做。那么现在的问题就是如何实现修改操作。如果暴力地做,则要对后缀中每个线段树进行修改,必 T。所以要对此进行优化。
回想一下 【模板】可持久化线段树 2。这道题我们在查询 \([l, r]\) 时并不单独在某棵线段树上面跑,而是将 \(r\) 和 \(l-1\) 的两棵线段树相减。把每棵线段树当做一个记录从 1 到 \(i\) 这个前缀每个数字出现次数的集合,记作 \(precnt_i\)。把 \([l, r]\) 每个数字出现次数的集合记作 \(cnt(l,r)\),则 \(cnt(l,r)=precnt_r-precnt_{l-1}\)。
可以发现这种形式就是查询前缀和再差分得到答案。由于树状数组在单点修改和查询前缀和上有着优秀的复杂度和常数,所以我们可以把线段树套在 BIT 里面。具体地,即每个 BIT 节点维护一个互不相交的(动态开点)线段树的根。
这种数据结构每次修改时使用类似 BIT 的方式,在 \(\log\) 棵线段树上更新。查询时同样类似树状数组,首先记下 \(l-1\) 和 \(r\) 各自对应的 \(\log\) 棵线段树,然后计算时用所有 \(r\) 对应的线段树减去所有 \(l-1\) 对应的线段树。
时间复杂度 \(O(n \log^2 n)\)。
核心代码如下。(为 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);
}
}
现在只需要把上面的东西搬到树上就行了。实际上这个和在序列上是本质相同的。树上差分即可。设 \(chain\_sum(a,b)\) 为从 \(a\) 到 \(b\) 的权值和,\(sum_i\) 为从根节点到 \(i\) 的权值和。由于树上差分的公式 \(chain\_sum(a, b) = sum_a + sum_b - sum_{lca} - sum_{fa_{lca}}\)。查询时用所有 \(a\) 和 \(b\) 对应的线段树减去 \(lca\) 和 \(fa_{lca}\) 对应的线段树。
注意修改时因为修改某个节点只会影响到自身及其子树,所以要消除对无关节点的影响。
为此需要求出每个节点的 DFS 序作为树状数组中的下标,使得每个节点及其子树中节点的下标在树状数组上是连续的,便于差分来消除对无关节点的影响。因为还要求 LCA,所以可用树链剖分实现这两个需求。
时间复杂度同上,为 \(O(n \log^2 n)\)。
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:修改部分描述使其更加严谨。

浙公网安备 33010602011771号