Kruskal重构树
用于解决图中,有关两点间路径最大/小边权最小/大值的问题
如将
按边权从小到大建立
重构树,我们就能得到这样的树
首先
重构树只有
个节点,只有
到
的点才
值
而原图中任意两点
间路径中最大边权的最小值可以在这颗树中找到,即
并且这颗树是一个大根堆,父节点的值大于或等于子节点的值
同理,如果我们按边权从大到小建立
重构树,我们就能得到这样的树
原图中任意两点
间路径中最小边权的最大值可以在这颗树中找到,即
并且这颗树是一个小根堆,父节点的值小于或等于子节点的值
模板
struct Edge {
int u, v, w;
bool operator < (const Edge &rhs) const {
return w > rhs.w;
//升序为(u, v)间多条路中最大边权最小值
//降序为(u, v)间多条路中最小边权最大值
}
} E[MAX];
vector<int> g[MAX];
int pre[MAX], val[MAX], cnt;//cnt为kruskal重构树的点数, 点数最多为2N - 1
int son[MAX], siz[MAX], top[MAX], fa[MAX], dep[MAX];
void dfs(int u, int par) {
dep[u] = dep[fa[u] = par] + (siz[u] = 1);
int max_son = -1;
for (auto &v: g[u])
if (v != par) {
dfs(v, u);
siz[u] += siz[v];
if (max_son < siz[v])
son[u] = v, max_son = siz[v];
}
}
void dfs2(int u, int topf) {
top[u] = topf;
if (!son[u]) return;
dfs2(son[u], topf);
for (auto &v: g[u])
if (v != fa[u] && v != son[u]) dfs2(v, v);
}
int lca(int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); }
void exKruskal() {
cnt = N; for (int i = 1; i < (N << 1); i++) pre[i] = i;
sort(E + 1, E + 1 + M);
for (int i = 1; i <= M; i++) {
int u = find(E[i].u), v = find(E[i].v);
if (u == v) continue;
val[++cnt] = E[i].w;
pre[u] = pre[v] = cnt;
g[u].push_back(cnt), g[cnt].push_back(u);
g[v].push_back(cnt), g[cnt].push_back(v);
if (cnt == (N << 1) - 1) break;
}
//原图不连通的情况, 那形成的就是森林
for (int i = 1; i <= cnt; i++)
if (!siz[i]) {//未访问过
int rt = find(i);//下树剖lca
dfs(rt, 0); dfs2(rt, rt);
}
}
P1967 货车运输
题目链接
题意
个点
条双向边的图,每条路有一个重量限制
现在有
次询问,每次询问
到
最多能运多重的物品
题解
询问,
到
有多条路径,只要你运的物品比要走的路径中最小的重量限制小就行,现在我们要尽可能的多运,所以要让最小值最大
即查询
到
的路径中最小边权的最大值
那就是一道
重构树的裸题了
按边权从大到小建立
重构树,这样
就是
到
的路径中最小边权的最大值
注意这里图没有说一定连通,所以要判断一下是不是连通
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 2e4 + 10;
const int MAX_M = 5e4 + 10;
int N, M, Q;
struct edge {
int u, v, w;
bool operator < (const edge &rhs) const {
return w > rhs.w;
//升序为(u, v)间多条路中最大边权最小值
//降序为(u, v)间多条路中最小边权最大值
}
} e[MAX_M];
vector<int> g[MAX];
int pre[MAX], val[MAX], cnt;//cnt为kruskal重构树的点数, 点数最多为2N - 1
//u, v为原图上的点, 则val[lca(u, v)]为u->v间多条路中...
//由于u, v可能在原图中不连通, 所以需要find(u)和find(v)判断一下是不是在一棵树中
int son[MAX], siz[MAX], top[MAX], fa[MAX], dep[MAX];
void dfs(int u, int par) {
dep[u] = dep[fa[u] = par] + (siz[u] = 1);
int max_son = -1;
for (auto &v: g[u])
if (v != par) {
dfs(v, u);
siz[u] += siz[v];
if (max_son < siz[v])
son[u] = v, max_son = siz[v];
}
}
void dfs2(int u, int topf) {
top[u] = topf;
if (!son[u]) return;
dfs2(son[u], topf);
for (auto &v: g[u])
if (v != fa[u] && v != son[u]) dfs2(v, v);
}
int lca(int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); }
void exKruskal() {
cnt = N; for (int i = 1; i < (N << 1); i++) pre[i] = i;
sort(e + 1, e + 1 + M);
for (int i = 1; i <= M; i++) {
int u = find(e[i].u), v = find(e[i].v);
if (u == v) continue;
val[++cnt] = e[i].w;
pre[u] = pre[v] = cnt;
g[u].push_back(cnt), g[cnt].push_back(u);
g[v].push_back(cnt), g[cnt].push_back(v);
if (cnt == (N << 1) - 1) break;//最多2N-1个点
}
for (int i = 1; i <= cnt; i++)
if (!siz[i]) {//未访问过
int rt = find(i);//下树剖lca
dfs(rt, 0); dfs2(rt, rt);
}
}
int main() {
scanf("%d%d", &N, &M);
for (int i = 1; i <= M; i++)
scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
exKruskal();
scanf("%d", &Q);
while (Q--) {
int u, v; scanf("%d%d", &u, &v);
if (find(u) != find(v)) printf("-1\n");
else printf("%d\n", val[lca(u, v)]);
}
return 0;
}
牛客练习赛62 E 水灾
题目链接
题意
个点 条双向边的连通图, 次询问,每次询问图中的 个互不相同的点,问最小的 使得删去所有 的边后这 个点互不连通
题解
要想让两点不能连通,那么两点间的所有路径都不能连通
一条路能够通,那么必须所有边的边权都
,那么要让他不连通,只需要这条路径中最小的边权
即可(即至少删掉一条边, 那就删掉权值最小的边),那么两点间有多条路径,要让这些路径中所有的最小边权都
,那就是让最大值
还有保证所有点都不连通,所以任意两个点之间的路径的最小边权的最大值都要
那么我们就可以按边权从大到小建立
重构树
两两之间求
会超时,考虑到
重构树是一个小根堆
我们只需要满足树上相邻的两点的
即可,因为越远两点的
的深度越小,其
越小
这里相邻可以将
个点按
序排序
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 1e6 + 10;
int N, M, Q;
int a[MAX];
//kruskal重构树 + 树剖lca
struct edge {
int u, v, w;
bool operator < (const edge &rhs) const {
return w > rhs.w;
}
} e[MAX];
struct edge2 {
int nxt, to;
} ee[MAX << 1];
int head[MAX], tot;
void add(int u, int v) { ee[++tot] = edge2{head[u], v}; head[u] = tot; }
int pre[MAX], val[MAX], cnt;
int son[MAX], siz[MAX], top[MAX], fa[MAX], dep[MAX], dfn[MAX], dfnt;
//树剖lca
void dfs(int u, int par) {
dfn[u] = ++dfnt;//记录dfs序
dep[u] = dep[fa[u] = par] + (siz[u] = 1);
int max_son = -1;
for (int i = head[u], v; i; i = ee[i].nxt)
if ((v = ee[i].to) != par) {
dfs(v, u);
siz[u] += siz[v];
if (max_son < siz[v])
son[u] = v, max_son = siz[v];
}
}
void dfs2(int u, int topf) {
top[u] = topf;
if (!son[u]) return;
dfs2(son[u], topf);
for (int i = head[u], v; i; i = ee[i].nxt)
if ((v = ee[i].to) != fa[u] && v != son[u]) dfs2(v, v);
}
int lca(int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
//kruskal重构树
int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); }
void exKruskal() {
cnt = N; for (int i = 1; i < (N << 1); i++) pre[i] = i;
sort(e + 1, e + 1 + M);
for (int i = 1; i <= M; i++) {
int u = find(e[i].u), v = find(e[i].v);
if (u == v) continue;
val[++cnt] = e[i].w;
pre[u] = pre[v] = cnt;
add(u, cnt), add(cnt, u);
add(v, cnt), add(cnt, v);
if (cnt == (N << 1) - 1) break;
}
int rt = find(1);
dfs(rt, 0); dfs2(rt, rt);
}
bool cmp(const int &x, const int &y) {
return dfn[x] < dfn[y];
}
int main() {
scanf("%d%d%d", &N, &M, &Q);
for (int i = 1; i <= M; i++) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
exKruskal();
int ans = 0;
while (Q--) {
int K; scanf("%d", &K);
for (int i = 1; i <= K; i++) scanf("%d", &a[i]), a[i] ^= ans;
sort(a + 1, a + 1 + K, cmp);
ans = 0;
for (int i = 2; i <= K; i++)
ans = max(ans, val[lca(a[i], a[i - 1])]);
printf("%d\n", ans);
}
return 0;
}
P4768 [NOI2018]归程
题目链接
题意
个点
条边的连通图,每条边有水位线
和长度
,若某天的最高水位线大于等于一条边的水位线,那么这条边车不能通过
现在
次询问,给你当日的最高水位线
,每次从点
出发回到点
,先乘车到达能够到达的点,剩下的路都步行(步行可以不考虑路的水位线),问最少走多长的路
题解
这里我们以以水位线为边权,
转换一下题意,其实就是找所有与
连通的点里面,到点
的最短距离
这里连通是满足两点间存在某条路径中最小的边权
也就是所有路径中最小边权的最大值
那么按边权从大到小建立
重构树
只需要找到出发点
的祖先中
的最大祖先节点
这样这个点及其子节点中所有的点与
都是连通的
这样我们只需要在一开始跑一个最短路,记
为
及其子树中距离
最近的距离,然后跑一个树形
即可
代码
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int MAX = 2e5 + 10;
const int MAX2 = 4e5 + 10;
int T;
int N, M, Q, K, S;
//最短路
struct edge {
int nxt, to, w;
} e[MAX2 << 1];
int head[MAX], tot;
void add(int u, int v, int w) { e[++tot] = edge{head[u], v, w}; head[u] = tot; }
void init() { tot = 0; for (int i = 1; i <= N; i++) head[i] = 0; }
int dis[MAX], vis[MAX];
struct node {
int now, d;
bool operator < (const node &rhs) const {
return d > rhs.d;
}
};
priority_queue<node> q;
void dijkstra(int s) {
while (!q.empty()) q.pop();
for (int i = 1; i <= N; i++) dis[i] = INF, vis[i] = 0;
dis[s] = 0;
q.push(node{s, dis[s]});
while (!q.empty()) {
node p = q.top(); q.pop();
int u = p.now;
if (vis[u]) continue;
vis[u] = 1;
for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
if (dis[u] + e[i].w < dis[v]) {
dis[v] = dis[u] + e[i].w;
if (!vis[v]) q.push(node{v, dis[v]});
}
}
}
//kruskal重构树和树形dp
struct Edge {
int u, v, w;
bool operator < (const Edge &rhs) const {
return w > rhs.w;
}
} E[MAX2];
vector<int> g[MAX2];
void add(int u, int v) { g[u].push_back(v); }
int pre[MAX2], val[MAX2], cnt;
int anc[MAX2][20], f[MAX2];
void dfs(int u, int fa) {//树形dp
f[u] = u <= N ? dis[u] : INF;//只有1-N的点才有距离
anc[u][0] = fa;
for (int i = 1; i <= 19; i++) anc[u][i] = anc[anc[u][i - 1]][i - 1];
for (auto &v: g[u])
if (v != fa) {
dfs(v, u);
f[u] = min(f[u], f[v]);//记录子树中最短距离
}
g[u].clear();//多组数据, 清空kruskal重构树的边
}
int getPoint(int u, int p) {
for (int i = 19; i >= 0; i--)
if (val[anc[u][i]] > p) u = anc[u][i];
return u;
}
int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); }
void exKruskal() {
cnt = N; for (int i = 1; i < (N << 1); i++) pre[i] = i, val[i] = 0;
sort(E + 1, E + 1 + M);
for (int i = 1; i <= M; i++) {
int u = find(E[i].u), v = find(E[i].v);
if (u == v) continue;
val[++cnt] = E[i].w;
pre[u] = pre[v] = cnt;
add(u, cnt), add(cnt, u);
add(v, cnt), add(cnt, v);
if (cnt == (N << 1) - 1) break;
}
int rt = find(1);
dfs(rt, 0);
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d%d", &N, &M);
init();
for (int i = 1; i <= M; i++) {
int u, v, l, w; scanf("%d%d%d%d", &u, &v, &l, &w);
E[i] = Edge{u, v, w};
add(u, v, l); add(v, u, l);
}
dijkstra(1);//先跑最短路
exKruskal();
int ans = 0;
scanf("%d%d%d", &Q, &K, &S);
while (Q--) {
int v0, p0; scanf("%d%d", &v0, &p0);
int v = (v0 + K * ans - 1) % N + 1, p = (p0 + K * ans) % (S + 1);
printf("%d\n", ans = f[getPoint(v, p)]);
}
}
return 0;
}
P4197 Peaks
题目链接
题意
座山峰,每座山峰有高度
,有些山峰间有双向道路相连,每条路有困难值。
现在有
组询问,每次询问从点
开始只经过困难值小于等于
的路径所能到达的山峰中第
高的山峰,若无解输出
题解
这里困难值就是路的边权,那么
在一个图中,对于点
来说,在
到
的所有路径中,只要有一条路径中最大边权
,那么我们就可以通过这条路到达
所以我们这里就是找所有路径中最大边权的最小值
因此按边权从小到大建立
重构树
这样我们只需要在
的祖先节点中找到
的最大祖先即可
然后再来看找高度第
大
我们可以用主席树来完成这个操作
这里我们按
序建树,并记录刚到一个点的时间
和搜完所有子树的时间
,这样我们要查一个点
及其子节点的信息的时候,只需要找
到
之间的点即可
代码
#include <bits/stdc++.h>
#define mid (l+r)/2
using namespace std;
typedef long long ll;
const int MAX = 2e5 + 10;
const int MAX_N = MAX * 30;
const int MAX_M = 5e5 + 10;
int N, M, Q;
int h[MAX];
//离散化和主席树
struct Hash {
int b[MAX], tot;
void init() { tot = 0; }
void insert(int x) { b[++tot] = x; }
void build() {
sort(b + 1, b + 1 + tot);
tot = unique(b + 1, b + 1 + tot) - (b + 1);
}
int pos(int x) { return lower_bound(b + 1, b + 1 + tot, x) - b; }
} ha;
int rt[MAX], tot;
int lc[MAX_N], rc[MAX_N], num[MAX_N];
void update(int &now, int pre, int l, int r, int p) {
now = ++tot;
num[now] = num[pre] + 1, lc[now] = lc[pre], rc[now] = rc[pre];
if (l == r) return;
if (p <= mid) update(lc[now], lc[pre], l, mid, p);
else update(rc[now], rc[pre], mid + 1, r, p);
}
int query(int now, int pre, int k) {
if (num[now] - num[pre] < k) return -1;//如果之间的数少于k个,返回-1
k = num[now] - num[pre] - k + 1;//这里主席树写的是第k小,转换一下变成第k大
int l = 1, r = ha.tot;
while (l < r) {
int sum = num[lc[now]] - num[lc[pre]];
if (k <= sum) {
now = lc[now], pre = lc[pre];
r = mid;
}
else {
now = rc[now], pre = rc[pre];
l = mid + 1;
k -= sum;
}
}
return ha.b[l];
}
//kruskal重构树
struct Edge {
int u, v, w;
bool operator < (const Edge &rhs) const { return w < rhs.w; }
} E[MAX_M];
vector<int> g[MAX];
int pre[MAX], val[MAX], cnt;
int anc[MAX][20], dep[MAX], dfn[MAX], nodeOf[MAX], dfnt;
int st[MAX], ed[MAX];
void dfs(int u, int fa) {
nodeOf[dfn[u] = ++dfnt] = u, anc[u][0] = fa, dep[u] = dep[fa] + 1;
for (int i = 1; i <= 19; i++) anc[u][i] = anc[anc[u][i - 1]][i - 1];
st[u] = dfnt;//开始的dfs序
for (auto &v: g[u])
if (v != fa) dfs(v, u);
ed[u] = dfnt;//结束的dfs序
}
int getPoint(int u, int p) {//找到u的祖先节点中满足val <= p的最大val所在的点
for (int i = 19; i >= 0; i--)
if (dep[u] > (1 << i) && val[anc[u][i]] <= p) u = anc[u][i];
return u;
}
int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); }
void exKruskal() {
cnt = N; for (int i = 1; i < (N << 1); i++) pre[i] = i;
sort(E + 1, E + 1 + M);
for (int i = 1; i <= M; i++) {
int u = find(E[i].u), v = find(E[i].v);
if (u == v) continue;
val[++cnt] = E[i].w;
pre[u] = pre[v] = cnt;
g[u].push_back(cnt), g[cnt].push_back(u);
g[v].push_back(cnt), g[cnt].push_back(v);
if (cnt == (N << 1) - 1) break;
}
for (int i = 1; i <= cnt; i++)
if (!dep[i]) {
int t = find(i);
dfs(t, 0);
}
}
int ask(int u, int p, int k) {
int point = getPoint(u, p);
return query(rt[ed[point]], rt[st[point] - 1], k);
}
int main() {
scanf("%d%d%d", &N, &M, &Q);
for (int i = 1; i <= N; i++) scanf("%d", &h[i]), ha.insert(h[i]);
ha.build();
for (int i = 1; i <= M; i++) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
E[i] = Edge{u, v, w};
}
exKruskal();
for (int i = 1; i <= dfnt; i++) {
rt[i] = rt[i - 1];
if (nodeOf[i] <= N)
update(rt[i], rt[i - 1], 1, ha.tot, ha.pos(h[nodeOf[i]]));
}
while (Q--) {
int v, x, k; scanf("%d%d%d", &v, &x, &k);
printf("%d\n", ask(v, x, k));
}
return 0;
}