[洛谷P4234] 最小差值生成树

时间:2023-03-09 01:44:05
[洛谷P4234] 最小差值生成树

题目类型:\(LCT\)动态维护最小生成树

传送门:>Here<

题意:求一棵生成树,其最大边权减最小边权最小

解题思路

和魔法森林非常像。先对所有边进行排序,每次加边的时候删除环上的最小边即可

正确性好像很显然,显然由于每一条边一定会被加入,所以最大边权是可以确定的,然后在所有小于等于自己的边权中已经尽量去除了最小的,这是一个贪心

问题在于,由于这里和魔法森林不一样,不要求一个路径上的,而是整颗生成树。因此单单利用\(split\)来维护好像有点棘手。

我们考虑我们是按从小到大的顺序加边的,而每一次又有环内最小的边。有点像一个队列……我们可以用一个队列来维护所有当前在最小生成树中的边,每一次更新队尾。由于是从小到大的,因此队头就是最大的,队尾就是最小的。由于每一次有可能有最小值出队,所以需要向后维护一下。如果出队的不是最小值,那么根本就不用去管。

至于有哪些边在最小生成树内,用个\(bool\)的桶来维护一下就好了,非常\(eazy\)

反思

这题竟然有自环……

另外,在这道题目里,\(splay\)应该维护最大值。而最大值在打擂的时候要注意儿子的存在问题。或者我们可以将\(val[0]\)设为无限大。

Code

/*By DennyQi 2018*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 50010;
const int MAXM = 200010;
const int MAXS = MAXN + MAXM;
const int INF = 1061109567;
inline int Max(const int a, const int b){ return (a > b) ? a : b; }
inline int Min(const int a, const int b){ return (a < b) ? a : b; }
inline int read(){
int x = 0; int w = 1; register char c = getchar();
for(; c ^ '-' && (c < '0' || c > '9'); c = getchar());
if(c == '-') w = -1, c = getchar();
for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w;
}
struct Edge{
int x,y,v;
}e[MAXM];
int N,M,ans(INF),MX,cnt;
int val[MAXS],mn[MAXS],q[MAXM],top,head=1;
bool used[MAXM];
struct LinkCutTree{
int ch[MAXS][2],fa[MAXS];
bool tag[MAXS];
inline bool rson(int f, int x){
return ch[f][1] == x;
}
inline bool isroot(int x){
return ch[fa[x]][rson(fa[x],x)]!=x;
}
inline void pushup(int x){
mn[x] = x;
if(val[mn[ch[x][0]]] < val[mn[x]] && ch[x][0]) mn[x] = mn[ch[x][0]];
if(val[mn[ch[x][1]]] < val[mn[x]] && ch[x][1]) mn[x] = mn[ch[x][1]];
}
inline void rotate(int x){
int f = fa[x], gf = fa[f];
bool p = rson(f,x), q = !p;
if(!isroot(f)) ch[gf][rson(gf,f)] = x; fa[x] = gf;
ch[f][p] = ch[x][q], fa[ch[x][q]] = f;
ch[x][q] = f, fa[f] = x;
pushup(f), pushup(x);
}
void reverse(int x){
if(!isroot(x)) reverse(fa[x]);
if(!tag[x]) return;
tag[x] = 0;
swap(ch[x][0], ch[x][1]);
tag[ch[x][0]] ^= 1, tag[ch[x][1]] ^= 1;
}
inline void splay(int x){
for(reverse(x); !isroot(x); rotate(x)){
if(isroot(fa[x])){ rotate(x); break; }
if(rson(fa[fa[x]],fa[x]) ^ rson(fa[x],x)) rotate(x); else rotate(fa[x]);
}
}
inline void access(int x){
for(int y = 0; x; y=x, x = fa[x]) splay(x), ch[x][1] = y, pushup(x);
}
inline void mroot(int x){
access(x);
splay(x);
tag[x] ^= 1;
}
inline int findroot(int x){
access(x), splay(x);
while(ch[x][0]) x = ch[x][0];
return x;
}
inline void split(int x, int y){
mroot(x);
access(y);
splay(y);
}
inline void link(int x, int y){
if(findroot(x) == findroot(y)) return;
mroot(x);
fa[x] = y;
}
inline void cut(int x, int y){
split(x,y);
if(ch[y][0] != x || ch[x][1] != 0) return;
ch[y][0] = fa[x] = 0;
}
inline void debug(){
for(int i = 1; i <= M+N; ++i){
printf("%d ls=%d rs=%d fa=%d min=%d %d\n",i,ch[i][0],ch[i][1],fa[i],val[mn[i]],mn[i]);
}
}
}qxz;
inline bool cmp(const Edge& a, const Edge& b){
return a.v < b.v;
}
int main(){
// freopen(".in","r",stdin);
N = read(), M = read();
for(int i = 1; i <= M; ++i){
e[i].x = read(), e[i].y = read(), e[i].v = read();
}
sort(e+1, e+M+1, cmp);
for(int i = 1; i <= N; ++i) val[i] = INF;
for(int i = N+1; i <= N+M; ++i) val[i] = e[i-N].v;
for(int i = 1; i <= N+M; ++i) mn[i] = i;
for(int i = 1; i <= M; ++i){
if(e[i].x == e[i].y) continue;
if(qxz.findroot(e[i].x) != qxz.findroot(e[i].y)){
qxz.link(e[i].x, N+i);
qxz.link(e[i].y, N+i);
MX = e[i].v;
++cnt;
used[i] = 1;
q[++top] = i;
}
else{
qxz.split(e[i].x, e[i].y);
int temp = mn[e[i].y]-N;
used[temp] = 0;
used[i] = 1;
qxz.cut(e[temp].x, temp+N);
qxz.cut(e[temp].y, temp+N);
qxz.link(e[i].x, N+i);
qxz.link(e[i].y, N+i);
q[++top] = i;
}
if(cnt == N-1){
qxz.split(1, N);
while(!used[q[head]]) ++head;
ans = Min(ans, val[q[top]+N] - val[q[head]+N]);
}
}
printf("%d", ans);
}