Kruskal重构树
用于解决图中,有关两点间路径最大/小边权最小/大值的问题
如将
按边权从小到大建立
K
r
u
s
k
a
l
Kruskal
Kruskal重构树,我们就能得到这样的树
首先
K
r
u
s
k
a
l
Kruskal
Kruskal重构树只有
2
N
−
1
2N-1
2N−1个节点,只有
N
N
N到
2
N
−
1
2N-1
2N−1的点才
v
a
l
val
val值
而原图中任意两点
u
,
v
u,v
u,v间路径中最大边权的最小值可以在这颗树中找到,即
v
a
l
[
l
c
a
(
u
,
v
)
]
val[lca(u, v)]
val[lca(u,v)]
并且这颗树是一个大根堆,父节点的值大于或等于子节点的值
同理,如果我们按边权从大到小建立
K
r
u
s
k
a
l
Kruskal
Kruskal重构树,我们就能得到这样的树
原图中任意两点
u
,
v
u,v
u,v间路径中最小边权的最大值可以在这颗树中找到,即
v
a
l
[
l
c
a
(
u
,
v
)
]
val[lca(u, v)]
val[lca(u,v)]
并且这颗树是一个小根堆,父节点的值小于或等于子节点的值
模板
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 货车运输
题目链接
题意
N
N
N个点
M
M
M条双向边的图,每条路有一个重量限制
w
w
w
现在有
Q
Q
Q次询问,每次询问
u
u
u到
v
v
v最多能运多重的物品
题解
询问,
u
u
u到
v
v
v有多条路径,只要你运的物品比要走的路径中最小的重量限制小就行,现在我们要尽可能的多运,所以要让最小值最大
即查询
u
u
u到
v
v
v的路径中最小边权的最大值
那就是一道
K
r
u
s
k
a
l
Kruskal
Kruskal重构树的裸题了
按边权从大到小建立
K
r
u
s
k
a
l
Kruskal
Kruskal重构树,这样
v
a
l
[
l
c
a
(
u
,
v
)
]
val[lca(u, v)]
val[lca(u,v)]就是
u
u
u到
v
v
v的路径中最小边权的最大值
注意这里图没有说一定连通,所以要判断一下是不是连通
代码
#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 水灾
题目链接
题意
N N N个点 M M M条双向边的连通图, Q Q Q次询问,每次询问图中的 K i K_i Ki个互不相同的点,问最小的 x x x使得删去所有 ≤ x \leq x ≤x的边后这 K i K_i Ki个点互不连通
题解
要想让两点不能连通,那么两点间的所有路径都不能连通
一条路能够通,那么必须所有边的边权都
>
x
>x
>x,那么要让他不连通,只需要这条路径中最小的边权
≤
x
\leq x
≤x即可(即至少删掉一条边, 那就删掉权值最小的边),那么两点间有多条路径,要让这些路径中所有的最小边权都
≤
x
\leq x
≤x,那就是让最大值
≤
x
\leq x
≤x
还有保证所有点都不连通,所以任意两个点之间的路径的最小边权的最大值都要
≤
x
\leq x
≤x
那么我们就可以按边权从大到小建立
K
r
u
s
k
a
l
Kruskal
Kruskal重构树
两两之间求
l
c
a
lca
lca会超时,考虑到
K
r
u
s
k
a
l
Kruskal
Kruskal重构树是一个小根堆
我们只需要满足树上相邻的两点的
v
a
l
>
x
val>x
val>x即可,因为越远两点的
l
c
a
lca
lca的深度越小,其
v
a
l
val
val越小
这里相邻可以将
K
K
K个点按
d
f
s
dfs
dfs序排序
代码
#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]归程
题目链接
题意
N
N
N个点
M
M
M条边的连通图,每条边有水位线
a
a
a和长度
l
l
l,若某天的最高水位线大于等于一条边的水位线,那么这条边车不能通过
现在
Q
Q
Q次询问,给你当日的最高水位线
S
S
S,每次从点
v
v
v出发回到点
1
1
1,先乘车到达能够到达的点,剩下的路都步行(步行可以不考虑路的水位线),问最少走多长的路
题解
这里我们以以水位线为边权,
转换一下题意,其实就是找所有与
v
v
v连通的点里面,到点
1
1
1的最短距离
这里连通是满足两点间存在某条路径中最小的边权
>
S
> S
>S
也就是所有路径中最小边权的最大值
>
S
>S
>S
那么按边权从大到小建立
K
r
u
s
k
a
l
Kruskal
Kruskal重构树
只需要找到出发点
v
v
v的祖先中
v
a
l
>
S
val > S
val>S的最大祖先节点
这样这个点及其子节点中所有的点与
v
v
v都是连通的
这样我们只需要在一开始跑一个最短路,记
f
i
f_i
fi为
i
i
i及其子树中距离
1
1
1最近的距离,然后跑一个树形
d
p
dp
dp即可
代码
#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
题目链接
题意
N
N
N座山峰,每座山峰有高度
h
i
h_i
hi,有些山峰间有双向道路相连,每条路有困难值。
现在有
Q
Q
Q组询问,每次询问从点
v
v
v开始只经过困难值小于等于
x
x
x的路径所能到达的山峰中第
k
k
k高的山峰,若无解输出
−
1
-1
−1
题解
这里困难值就是路的边权,那么
在一个图中,对于点
u
u
u来说,在
u
u
u到
v
v
v的所有路径中,只要有一条路径中最大边权
≤
x
\leq x
≤x,那么我们就可以通过这条路到达
v
v
v
所以我们这里就是找所有路径中最大边权的最小值
因此按边权从小到大建立
K
r
u
s
k
a
l
Kruskal
Kruskal重构树
这样我们只需要在
v
v
v的祖先节点中找到
v
a
l
≤
x
val\leq x
val≤x的最大祖先即可
然后再来看找高度第
K
K
K大
我们可以用主席树来完成这个操作
这里我们按
d
f
s
dfs
dfs序建树,并记录刚到一个点的时间
s
t
a
r
t
start
start和搜完所有子树的时间
e
n
d
end
end,这样我们要查一个点
u
u
u及其子节点的信息的时候,只需要找
s
t
a
r
t
start
start到
e
n
d
end
end之间的点即可
代码
#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;
}