这个东西可以用来搞一搞图的连通性啊,在最小 / 最大生成树上维护一些东西的时候可以用到。
构造方法:进行 kruskal 的过程中,对于可以合并的一条边的两个节点所在的集合的根节点,新开一个节点(表示这条边),然后这个节点就作为这俩根节点的父亲节点,这样形成的一棵树就是 kruskal 重构树。其中这条边的边权化为这个新节点的点权。
说起来有点抽象,不如来直接看图:
(画得很丑见谅......)
按照 kruskal 算法的流程,每条边按照权值排序之后依次把两个点尝试合并到一个集合内,如果本来就在一个集合就不合并。
第一次合并之后结果如图:
绿色的那个点就是新建的点,点权就是 <2,5> 这条边的边权。
第二次合并之后如图:
第二次找到的边是 <4,5> 这条,然后 5 被合并过,其对应的根节点为 6,这个时候就需要把 6 和 4 合并,并把新节点 7 作为 4 和 6 的父亲,点权就是 <4,5> 的边权。
之后的合并就依此类推,第三次合并之后如图:
第四次合并之后如图:
这个时候,所有的点都被合并上了,至此,重构树就构造好了。
当然直接看这个图还是挺难看的......把重构树单独提取出来看看?
这个图就极其直观了:
- 显然是二叉树,满足大根堆的性质(即父亲的点权大于等于左右儿子的权值);
- 原图的节点都是叶子节点;
- 两个叶子的 LCA 的权值就是:从 u 点走到 v 点的所有路径中,经过的最大边权最小是多少。
然后就可以对这棵树进行各种维护了......比如跑个 dfs 序然后上线段树啊,比如树上倍个增啊什么的。
而且,这个东西可以一定程度上维护图的连通性(当然,没有 LCT 那么强大)。
来看些例题吧
也算是很经典的一个题了吧~
首先可以 dijkstra 求出每个点到 1 的最短路,之后按照海拔从大到小对边排序,建出 kruskal 重构树,不难发现对于低于特定的海拔,一个点能够到达的点集在重构树上他们都在同一棵子树内。所以就可以用线段树维护 dfs 序,然后每次询问需要找到 v 这个叶子上对应的海拔恰好大于 p 的最近的祖先,这个直接倍增往上跳就可以了。线段树维护的就是最短路 dis 的最小值。
这个题就很愉快地做完了~时间复杂度为 $\mathcal{O}(T(n\log n+Q\log n))$
#include <bits/stdc++.h>
#define ll long long
#define ls id << 1
#define rs id << 1 | 1
#define mem(array, value, size, type) memset(array, value, ((size) + 5) * sizeof(type))
#define memarray(array, value) memset(array, value, sizeof(array))
#define fillarray(array, value, begin, end) fill((array) + (begin), (array) + (end) + 1, value)
#define fillvector(v, value) fill((v).begin(), (v).end(), value)
#define pb(x) push_back(x)
#define st(x) (1LL << (x))
#define pii pair<int, int>
#define mp(a, b) make_pair((a), (b))
#define Flush fflush(stdout)
#define vecfirst (*vec.begin())
#define veclast (*vec.rbegin())
#define vecall(v) (v).begin(), (v).end()
#define vecupsort(v) (sort((v).begin(), (v).end()))
#define vecdownsort(v, type) (sort(vecall(v), greater<type>()))
#define veccmpsort(v, cmp) (sort(vecall(v), cmp))
using namespace std;
const int N = 500050;
const int inf = 0x3f3f3f3f;
const ll llinf = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
const int MOD = 1e9 + 7;
const double PI = acos(-1.0);
clock_t TIME__START, TIME__END;
void program_end()
{
#ifdef VINGYING
printf("\n\nTime used:
system("pause");
#endif
}
int n, m, f[N], in[N], out[N], tim, dep[N];
ll p[N][20], a[N], father[N][20];
int Find(int x) { return x == f[x] ? x : f[x] = Find(f[x]); }
vector<int> e2[N];
bool Union(int x, int y, ll height)
{
int fx = Find(x), fy = Find(y);
if (fx != fy)
{
n++;
f[n] = n, f[fx] = n, f[fy] = n;
e2[n].push_back(fy);
e2[n].push_back(fx);
a[n] = height;
return true;
}
return false;
}
void dfs(int u, int fa)
{
in[u] = ++tim;
dep[u] = dep[fa] + 1;
father[u][0] = fa;
p[u][0] = a[fa];
for (int i = 1; i < 20; ++i)
{
p[u][i] = p[father[u][i - 1]][i - 1];
father[u][i] = father[father[u][i - 1]][i - 1];
}
for (auto v : e2[u])
{
if (v == fa)
continue;
dfs(v, u);
}
out[u] = tim;
}
int jump(int u, ll height)
{
for (int i = 19; i >= 0; --i)
{
if (dep[u] - st(i) > 0 && p[u][i] > height)
u = father[u][i];
}
return u;
}
struct edge
{
int u, v;
ll length, height;
} e[N];
bool cmpedge(const edge &x, const edge &y) { return x.height > y.height; }
vector<edge> g[N];
ll dis[N];
bool vis[N];
struct node
{
int u;
ll length;
bool operator<(const node &x) const
{
return length > x.length;
}
};
void dijkstra()
{
priority_queue<node> pq;
mem(dis, inf, n, ll);
mem(vis, 0, n, bool);
dis[1] = 0;
pq.push((node){1, 0});
while (!pq.empty())
{
node u = pq.top();
pq.pop();
if (vis[u.u])
continue;
vis[u.u] = 1;
for (auto v : g[u.u])
{
if (dis[v.v] > dis[u.u] + v.length)
{
dis[v.v] = dis[u.u] + v.length;
pq.push((node){v.v, dis[v.v]});
}
}
}
}
struct segtree
{
int l, r;
ll mn;
} t[N << 2];
inline void pushup(int id) { t[id].mn = min(t[ls].mn, t[rs].mn); }
void build(int l, int r, int id)
{
t[id].l = l, t[id].r = r, t[id].mn = llinf;
if (l == r)
return;
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
}
void change(int pos, ll val, int id)
{
int l = t[id].l, r = t[id].r;
if (l == r)
{
t[id].mn = val;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
change(pos, val, ls);
else
change(pos, val, rs);
pushup(id);
}
ll query(int L, int R, int id)
{
int l = t[id].l, r = t[id].r;
if (L <= l && R >= r)
return t[id].mn;
int mid = (l + r) >> 1;
if (R <= mid)
return query(L, R, ls);
else if (L > mid)
return query(L, R, rs);
else
return min(query(L, mid, ls), query(mid + 1, R, rs));
}
void data_clear()
{
memarray(a, inf);
for (int i = 0; i <= n; ++i)
e2[i].clear(), g[i].clear();
tim = 0;
}
inline void solve()
{
scanf(
int nn = n;
for (int i = 1; i <= m; ++i)
{
int u, v, l, a;
scanf(
e[i].height = a, e[i].length = l, e[i].u = u, e[i].v = v;
g[u].push_back((edge){u, v, l, a});
g[v].push_back((edge){v, u, l, a});
}
dijkstra();
sort(e + 1, e + m + 1, cmpedge);
for (int i = 1; i <= n; ++i)
f[i] = i;
int tot = 0;
for (int i = 1; i <= m; ++i)
{
int x = e[i].u, y = e[i].v, h = e[i].height;
tot += Union(x, y, h);
if (tot == nn - 1)
break;
}
for (int i = 1; i <= n; ++i)
if (Find(i) == i)
dfs(i, 0);
build(1, tim, 1);
for (int i = 1; i <= nn; ++i)
change(in[i], dis[i], 1);
int Q, K, S;
ll lastans = 0;
scanf(
while (Q--)
{
int v, p;
scanf(
v = (v + K * lastans - 1)
int u = jump(v, p);
ll ans = query(in[u], out[u], 1);
lastans = ans;
printf(
}
data_clear();
}
int main()
{
// freopen("return.in", "r", stdin);
// freopen("return.out", "w", stdout);
TIME__START = clock();
int Test = 1;
scanf(
while (Test--)
{
solve();
// if (Test)
// putchar('\n');
}
TIME__END = clock();
program_end();
return 0;
}
Codeforces 1416D - Graph and Queries
这个题需要搞搞连通性......
如果把两个连通分量的重构树连起来的话,不难发现连通分量各自内部的 dfs 序是不会变的,那么这个题就可以把所有操作都倒过来,删边变成加边。
先求出所有操作弄完之后的每个连通分量的 kruskal 重构树,之后倒过来每次加边就还是按照重构树那样来构造,只不过这样可以保证 dfs 序不会乱。
倒过来加边处理完之后,可能最后还是有多个连通分量,每个连通分量求一次 dfs 序即可。所有的 dfs 序放在线段树上一起维护即可。
然后对于操作 1 倒过来的话,可以看成每次操作都是去查询这个连通分量的并查集的根节点,那就直接把查询的这个点变成当前集合的根节点即可。这样后续加边的时候这个点会转移到这个连通分量的子树的根上,方便后面的操作正序过来的询问。
总之,在构建 kruskal 重构树的时候,顺序是很重要的!一定要理清楚每一步的意义!
#include <bits/stdc++.h>
#define ll long long
#define ls id << 1
#define rs id << 1 | 1
#define mem(array, value, size, type) memset(array, value, ((size) + 5) * sizeof(type))
#define memarray(array, value) memset(array, value, sizeof(array))
#define fillarray(array, value, begin, end) fill((array) + (begin), (array) + (end) + 1, value)
#define fillvector(v, value) fill((v).begin(), (v).end(), value)
#define pb(x) push_back(x)
#define st(x) (1LL << (x))
#define pii pair<int, int>
#define mp(a, b) make_pair((a), (b))
#define Flush fflush(stdout)
#define vecfirst (*vec.begin())
#define veclast (*vec.rbegin())
#define vecall(v) (v).begin(), (v).end()
#define vecupsort(v) (sort((v).begin(), (v).end()))
#define vecdownsort(v, type) (sort(vecall(v), greater<type>()))
#define veccmpsort(v, cmp) (sort(vecall(v), cmp))
using namespace std;
const int N = 1000050;
const int inf = 0x3f3f3f3f;
const ll llinf = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
const int MOD = 1e9 + 7;
const double PI = acos(-1.0);
clock_t TIME__START, TIME__END;
void program_end()
{
#ifdef ONLINE
printf("\n\nTime used:
system("pause");
#endif
}
int n, m, Q, p[N], f[N], in[N], out[N], tim, del[N];
vector<int> g[N];
int Find(int x) { return x == f[x] ? x : f[x] = Find(f[x]); }
void Union(int x, int y)
{
int fx = Find(x), fy = Find(y);
if (fx != fy)
{
++n;
f[n] = n, f[fx] = n, f[fy] = n;
g[n].push_back(fx), g[n].push_back(fy);
}
}
void dfs_tim(int u, int fa)
{
in[u] = ++tim;
for (auto v : g[u])
{
if (v != fa)
dfs_tim(v, u);
}
out[u] = tim;
}
struct querys
{
int type;
int val;
} q[N];
struct edges
{
int u, v;
} e[N];
struct segtree
{
int l, r;
pii mx;
} t[N << 2];
inline void pushup(int id) { t[id].mx = max(t[ls].mx, t[rs].mx); }
void build(int l, int r, int id)
{
t[id].l = l, t[id].r = r;
if (l == r)
return;
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
}
void change(int pos, pii val, int id)
{
int l = t[id].l, r = t[id].r;
if (l == r)
{
t[id].mx = val;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
change(pos, val, ls);
else
change(pos, val, rs);
pushup(id);
}
pii query(int L, int R, int id)
{
int l = t[id].l, r = t[id].r;
if (L <= l && R >= r)
return t[id].mx;
int mid = (l + r) >> 1;
if (R <= mid)
return query(L, R, ls);
else if (L > mid)
return query(L, R, rs);
else
return max(query(L, mid, ls), query(mid + 1, R, rs));
}
inline void solve()
{
scanf(
int nn = n;
for (int i = 1; i <= n; ++i)
scanf(
for (int i = 1; i <= m; ++i)
{
int x, y;
scanf(
e[i] = (edges){x, y};
}
for (int i = 1; i <= Q; ++i)
{
scanf(
if (q[i].type == 2)
del[q[i].val] = 1;
}
for (int i = 1; i <= m; ++i)
if (del[i] == 0)
Union(e[i].u, e[i].v);
for (int i = Q; i; --i)
{
if (q[i].type == 2)
Union(e[q[i].val].u, e[q[i].val].v);
else
q[i].val = Find(q[i].val);
}
for (int i = 1; i <= n; ++i)
if (Find(i) == i)
dfs_tim(i, 0);
build(1, tim, 1);
for (int i = 1; i <= nn; ++i)
change(in[i], mp(p[i], i), 1);
for (int i = 1; i <= Q; ++i)
{
if (q[i].type == 1)
{
int u = q[i].val;
pii ans = query(in[u], out[u], 1);
printf(
int node = ans.second;
if (in[node])
change(in[node], mp(0, 0), 1);
}
}
}
int main()
{
TIME__START = clock();
int Test = 1;
// scanf(
while (Test--)
{
solve();
// if (Test)
// putchar('\n');
}
TIME__END = clock();
program_end();
return 0;
}
总而言之,kruskal 重构树可以处理最小 / 最大生成树的一些问题,可以套 dfs 序、线段树什么的来维护一些东西,将边权转为点权来实现,较为方便。
并且一定要注意顺序!
Comments 1 条评论
博主 soer
大佬是如何管理学习与娱乐时间的