Description

给定一个 \(n\) 个顶点,\(m\) 条边的无向联通图,点、边带权。

先有 \(q\) 次修改或询问,每个指令形如 \(\text{opt}\ x\ y\):

  • \(\text{opt}=1\):将顶点 \(x\) 的点权修改为 \(y\);
  • \(\text{opt}=2\):查询顶点 \(x, y\) 间所有路径中路径上最大值中,最小的哪一个最大值(瓶颈路)。
  • \(\text{opt}=3\):查询顶点 \(x\) 可以结果边权 \(\le y\) 的边能到达的所有点上有几种不同的点权。

Hint

  • \(1\le n\le 10^5, 1\le m\le 3\times 10^5, 1\le q\le 2\times 10^5\)
  • \(\text{点权、边权}\in[0, 2^{31})\)

Solution

首先对于 \(\text{opt}=2\) 的操作,这是个经典问题,我们有很多解决思路。但是看到操作三就发现 Kruskal 重构树才是最好的选择。

我们假设没有操作一,那么操作二可以转化为重构树上两个结点的 LCA,操作三则是子树数颜色。

考虑到一颗子树的 dfs 序连续,那么这又可以转化为序列问题,于是成了区间数颜色。

然而在加上修改操作操作三就变的棘手了,或者树套树应该也能过但肯定不好写。


一看清一色待修莫队,感觉这个题可以不用这样麻烦。

之后在题解区发现了 mrsrz 的题解 的一只 \(\log\) 处理方法,感觉很妙,于是学习一波。

对于一个在结点 \(x\) 刚插入的一种颜色 \(c\),它可以贡献的范围是 \(x\) 的一个深度最浅的祖先 \(a\) 满足以 \(a\) 为根的子树中原本不存在任何一个颜色 \(c\)。于是我们就可以在这上面做链加。大力树剖加树状数组是 \(O(\log^2n)\) 一次的,但直接树上差分则可以做到 \(O(\log n)\)。具体地,我们在结点 \(x\) 的位置 \(+1\),然后在 \(a\) 的父亲上 \(-1\),因为它可以贡献到的最高的位置是 \(a\)。

然后就是如何找到这样一个 \(a\) 的问题。其实这个不难处理,这个 \(a\) 必然是重构树上 dfs 序与 \(x\) 相邻的两个结点(有可能一个)\(y_1, y_2\) 的两个 \(\text{LCA}(x, y_1), \text{LCA}(x, y_2)\) 中,深度较深的那一个。如果对虚树比较熟那么这就很显然。

具体实现时,为了找到 dfs 序相邻的点,我们对每一种颜色开一个 std::set,存这个颜色的所有结点并按 dfs 序排序。

这样总复杂度是 \(O((n+m+q)\log n)\) 的。

Code

这个题非常码农,所以写的有点长。不过思路还是很清晰的。

/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : Luogu P5168 xtq玩魔塔
*/
#include <algorithm>
#include <cstdio>
#include <cctype>
#include <map>
#include <set>
#include <vector> inline int read() {
int x(0), s(0); char c; while (!isgraph(c = getchar()));
if (x == '-') s = 1, c = getchar();
do x = (x << 1) + (x << 3) + c - 48; while (isdigit(c = getchar()));
return s ? -x : x;
} const int N = 1e5 + 5;
const int M = 3e5 + 5;
const int Q = 2e5 + 5;
const int V = N << 1;
const int logN = 19; int n, m, q, a[N];
struct Edge {
int u, v, w;
bool operator < (const Edge& rhs) const {
return w < rhs.w;
}
} e[M]; int uset[V];
int find(int x) {
return x == uset[x] ? x : uset[x] = find(uset[x]);
} int vcnt;
int ch[V][2], fa[V][logN], val[V]; int timer(0);
int dfn[V], siz[V], dep[V]; void dfs(int x) {
dfn[x] = ++timer, siz[x] = 1, dep[x] = dep[fa[x][0]] + 1;
if (!ch[x][0] && !ch[x][1]) return;
dfs(ch[x][0]), dfs(ch[x][1]), siz[x] += siz[ch[x][0]] + siz[ch[x][1]];
}
int lca(int x, int y) {
if (dep[x] < dep[y]) std::swap(x, y);
for (int j = logN - 1; ~j; j--)
if (dep[fa[x][j]] >= dep[y]) x = fa[x][j];
if (x == y) return x;
for (int j = logN - 1; ~j; j--)
if (fa[x][j] != fa[y][j]) x = fa[x][j], y = fa[y][j];
return fa[x][0];
}
int getanc(int x, int y) {
for (int j = logN - 1; ~j; --j)
if (fa[x][j] && val[fa[x][j]] <= y) x = fa[x][j];
return x;
} namespace bit {
int tr[V];
void add(int p, int v) {
for (; p <= vcnt; p += p & -p) tr[p] += v;
}
int get(int p) {
int v(0);
for (; p; p -= p & -p) v += tr[p];
return v;
}
} struct cmp {
bool operator () (const int& a, const int& b) {
return dfn[a] < dfn[b];
}
};
int col_tot(0);
std::map<int, int> idx;
std::set<int, cmp> pos[V + Q]; int getIdx(int col) {
return idx.count(col) ? idx[col] : idx[col] = ++col_tot;
}
void update_col(int x, int c) {
bit::add(dfn[x], 1);
std::set<int>::iterator it = pos[c = getIdx(c)].insert(x).first;
if (pos[c].size() == 1u) return; std::vector<int> adj; adj.reserve(2);
if (++it != pos[c].end()) adj.push_back(*it);
if (--it != pos[c].begin()) adj.push_back(*--it); std::pair<int, int> y;
for (int i = 0; i < (int)adj.size(); i++) {
int l = lca(x, adj[i]);
y = std::max(y, std::make_pair(dep[l], l));
}
bit::add(dfn[y.second], -1);
}
void remove_col(int x, int c) {
bit::add(dfn[x], -1);
std::set<int>::iterator it = pos[c = getIdx(c)].find(x);
if (pos[c].size() == 1u) { pos[c].erase(it); return; } std::vector<int> adj; adj.reserve(2);
if (++it != pos[c].end()) adj.push_back(*it);
if (--it != pos[c].begin()) adj.push_back(*--it), ++it;
pos[c].erase(it); std::pair<int, int> y;
for (int i = 0; i < (int)adj.size(); i++) {
int l = lca(x, adj[i]);
y = std::max(y, std::make_pair(dep[l], l));
}
bit::add(dfn[y.second], 1);
} int count(int x, int y) {
x = getanc(x, y);
return bit::get(dfn[x] + siz[x] - 1) - bit::get(dfn[x] - 1);
} signed main() {
n = read(), m = read(), q = read();
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i <= m; i++) e[i].u = read(), e[i].v = read(), e[i].w = read(); std::sort(e + 1, e + 1 + m), vcnt = n;
for (int i = 1; i <= n; i++) uset[i] = i;
for (int i = 1; i <= m && vcnt != n * 2 - 1; i++) {
int u = find(e[i].u), v = find(e[i].v);
if (u == v) continue;
val[++vcnt] = e[i].w, uset[u] = uset[v] = uset[vcnt] = vcnt;
fa[ch[vcnt][0] = u][0] = fa[ch[vcnt][1] = v][0] = vcnt;
}
for (int j = 1; j < logN; j++)
for (int i = 1; i <= vcnt; i++)
fa[i][j] = fa[fa[i][j - 1]][j - 1];
dfs(vcnt); for (int i = 1; i <= n; i++)
update_col(i, a[i]); while (q--) {
int opt = read(), x = read(), y = read();
if (opt == 1) remove_col(x, a[x]), update_col(x, a[x] = y);
if (opt == 2) printf("%d\n", val[lca(x, y)]);
if (opt == 3) printf("%d\n", count(x, y));
}
return 0;
}

【Luogu P5168】xtq玩魔塔(Kruskal 重构树 & 树状数组 & set)的更多相关文章

  1. Luogu P5168 xtq玩魔塔

    这题不错啊,结合了一些不太传统的姿势. 首先看到题目有一问从一个点到另一个点边权最小值.想到了什么? 克鲁斯卡尔生成树+倍增?好吧其实有一个更常用NB的算法叫克鲁斯卡尔重构树 (不会的可以看dalao ...

  2. P5168 xtq玩魔塔 [克鲁斯卡尔重构树+带修莫队]

    P5168 xtq玩魔塔 又是码农题- 利用克鲁斯卡尔重构树的性质 我们就可以得出 \(dep\) 值小的,肯定比 \(dep\) 大的值要优. 于是第二问就可以直接 LCA 求出来了- 至于第三问, ...

  3. Luogu P4768 [NOI2018]归程(Dijkstra+Kruskal重构树)

    P4768 [NOI2018]归程 题面 题目描述 本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定. 魔力之都可以抽象成一个 \(n\) 个节点. \(m\) 条边的无向连通图(节点的编 ...

  4. luogu P3031 [USACO11NOV]高于中位数Above the Median (树状数组优化dp)

    链接:https://www.luogu.org/problemnew/show/P3031 题面: 题目描述 Farmer John has lined up his N (1 <= N &l ...

  5. P5168 xtq玩魔塔

    传送门 其实就是板子--只要会克鲁斯卡尔重构树和带修莫队就可以了 这么想着的我就调了将近一个下午-- 思路其实比较清晰,然而码量很大,细节贼多-- 不难看出只在最小生成树上走最优,于是建出克鲁斯卡尔重 ...

  6. Luogu P5103 「JOI 2016 Final」断层 树状数组or线段树+脑子

    太神仙了这题... 原来的地面上升,可以倒着操作(时光倒流),转化为地面沉降,最后的答案就是每个点的深度. 下面的1,2操作均定义为向下沉降(与原题意的变换相反): 首先这个题目只会操作前缀和后缀,并 ...

  7. [luogu P4197] Peaks 解题报告(在线:kruskal重构树+主席树 离线:主席树+线段树合并)

    题目链接: https://www.luogu.org/problemnew/show/P4197 题目: 在Bytemountains有N座山峰,每座山峰有他的高度$h_i$.有些山峰之间有双向道路 ...

  8. Luogu P1967 货车运输(Kruskal重构树)

    P1967 货车运输 题面 题目描述 \(A\) 国有 \(n\) 座城市,编号从 \(1\) 到 \(n\) ,城市之间有 \(m\) 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在有 \ ...

  9. Luogu4899 IOI2018 Werewolf 主席树、Kruskal重构树

    传送门 IOI强行交互可还行,我Luogu的代码要改很多才能交到UOJ去-- 发现问题是对边权做限制的连通块类问题,考虑\(Kruskal\)重构树进行解决. 对于图上的边\((u,v)(u<v ...

随机推荐

  1. WSL2:我在原生的Win10玩转Linux系统

    原文地址:梁桂钊的博客 博客地址:http://blog.720ui.com 欢迎关注公众号:「服务端思维」.一群同频者,一起成长,一起精进,打破认知的局限性. WSL2:我在原生的Win10玩转Li ...

  2. maven pom.xml 报错

    首先介绍背景,在eclipse中导入一个maven的项目,在我之前的电脑上导入好用,在自己的电脑上导入居然pom报错了Missing artifact junit:junit:jar:4.11,还会有 ...

  3. Xpath定位元素-一个例子

    前几天在群里面解决的问题,记录下来和大家分享 需要定位这个股份制企业 方法: # 首先需要单击下拉框弹出企业性质的下拉选项:然后用过Xpath定位元素 driver.find.element_by_c ...

  4. HDU100题简要题解(2000~2009)

    前言(废话): 从11月6号到11月20号,断断续续做了有三个星期,总算整完了,之后会慢慢整理汇总到这里 中间部分用到数学知识的十几道题边学边做直接把我这个数学菜鸟做到怀疑人生 11.6~11.10又 ...

  5. ABBYY FineReader 15 PDF有哪些好用的功能?

    ABBYY FineReader 15(Windows系统)OCR文字识别软件中的PDF编辑器,是一个对用户相当友好的编辑器,不仅可以在其中查看,搜索PDF文档,还可以用以编辑文本,添加备注,添加与删 ...

  6. CorelDRAW常用工具之渐变工具

    我们在进行宣传单页或者LOGO等等各种平面设计时,颜色的使用是极为重要的一方面.有些新手可能还不知道怎么填充多种颜色的渐变,有的背景色不止2个颜色渐变,而是由多种颜色调成的. 我们在画布上画两个图形, ...

  7. jQuery 第九章 工具方法之插件扩展 $.extend() 和 $.fn.extend()

    $.extend() $.fn.extend() -------------------------------------------------- $.extend() 插件扩展(工具方法) jq ...

  8. 小程序ui自动化(一),用uiAutormatorViewer定位元素失败,如何解决

    1.定位元素 用android ADT自带工具:uiAutormatorViewer,会报如下错误 可能是环境与手机不兼容 可以用以下方法解决:(参考:https://blog.csdn.net/qq ...

  9. IPSec传输模式/隧道模式下ESP报文的装包与拆包过程

    IPSec协议:IPsec将IP数据包的内容先加密再传输,即便中途被截获,由于缺乏解密数据包所必要的密钥,攻击者也无法获取里面的内容. 传输模式和隧道模式:IPsec对数据进行加密的方式有两种:传输模 ...

  10. C语言printf()函数的格式化字符串

    原文链接:https://www.runoob.com/cprogramming/c-function-printf.html#include<stdio.h> #include<s ...