做题记录:P4234 最小差值生成树
这道题我之前用 kruskal 乱搞过了,于是我今天下定决心用正解 A 这道题。
于是我为此去学习了 LCT。
LCT 的操作建议去模板看看。
对于这道题,很显然我们可以贪心。
排好序(从小到大)后不断连边,如果出现环就找环上最大的边,如果是当前边就连上然后断掉最小边。
然后继续往后找,如果边到达限制条数就更新答案,因为从小到大排过序,于是我们直接拿当前边权减去之前访问过的最长边即可。
备注一个东西:
set 为啥是二元组,因为相同的边权可能有很多,所以第二个的下标值主要是为了防止重复和方便查找,如果直接手写平衡树,那么不用二元组。
每次循环长这样:
int u=e[i].u,v=e[i].v,w=e[i].w;
if(u==v)continue;
val[i+n]=w;
if(find(u)!=find(v)){
f[find(u)]=find(v);cnt++;
lct.link(i+n,u);lct.link(i+n,v);//新开一个中转点,把uv连上
st.insert({w,i});//把当前边权放入集合中
}else{
lct.split(u,v);//打通uv的路径(找环)
int va=lct.mn[v],id=lct.id[v]-n;//找到这条路径上最小边
lct.cut(id+n,e[id].u);lct.cut(id+n,e[id].v);//断掉最小边
lct.link(i+n,u),lct.link(i+n,v);//连上当前边
st.erase({e[id].w,id});//把最小边从集合里删去
st.insert({w,i});//把当前边加入集合
}
if(cnt==n-1)ans=min(ans,w-st.begin()->first);//如果边数足够更新答案
然后完整的代码放一下,防止自己脑抽筋忘了,这道题的 lct 和别的不一样的是:
pushup 操作要维护最小值和最小值的点的名称。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<set>
#define N 1919810
using namespace std;
struct edge{
int u,v,w;
void in(){scanf("%d%d%d",&u,&v,&w);}
bool operator<(const edge &c)const{return w<c.w;}
}e[N];
set<pair<int,int> >st;
int val[N],n,m,f[N],cnt,ans=2147483647;
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
struct link_cut_tree{
int ch[N][2],f[N],mn[N],id[N],st[N];
bool r[N];
#define lc ch[x][0]
#define rc ch[x][1]
void pushup(int x){
mn[x]=val[x];id[x]=x;
if(mn[x]>mn[lc]&&lc)mn[x]=mn[lc],id[x]=id[lc];
if(mn[x]>mn[rc]&&rc)mn[x]=mn[rc],id[x]=id[rc];
}
int son(int x){return x==ch[f[x]][1];}
int nroot(int x){return x==ch[f[x]][0]||x==ch[f[x]][1];}
void rev(int x){swap(lc,rc),r[x]^=1;}
void pushdown(int x){
if(r[x]){
if(lc)rev(lc);
if(rc)rev(rc);
r[x]=0;
}
}
void rotate(int x){
int y=f[x],z=f[y],k=son(x),w=ch[x][!k];
if(nroot(y))ch[z][son(y)]=x;
ch[x][!k]=y;ch[y][k]=w;
if(w)f[w]=y;
f[y]=x,f[x]=z;
pushup(y);
}
void splay(int x){
int y=x,z=0;
st[++z]=y;
while(nroot(y))st[++z]=y=f[y];
while(z)pushdown(st[z--]);
while(nroot(x)){
y=f[x];
if(nroot(y))rotate(son(x)==son(y)?y:x);
rotate(x);
}
pushup(x);
}
void access(int x){for(int y=0;x;x=f[y=x])splay(x),rc=y,pushup(x);}
void makeroot(int x){access(x);splay(x);rev(x);}
int findroot(int x){
access(x);splay(x);
while(lc)pushdown(x),x=lc;
splay(x);
return x;
}
void split(int x,int y){makeroot(x);access(y);splay(y);}
void link(int x,int y){
makeroot(x);
if(findroot(y)==x)return;
f[x]=y;
}
void cut(int x,int y){
makeroot(x);
if(findroot(y)==x&&f[y]==x&&!ch[y][0]){
f[y]=rc=0;
pushup(x);
}
}
}lct;
signed main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)e[i].in();
sort(e+1,e+m+1);
for(int i=1;i<=n;i++)f[i]=i,val[i]=2147483647;
for(int i=1;i<=m;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
if(u==v)continue;
val[i+n]=w;
if(find(u)!=find(v)){
f[find(u)]=find(v);cnt++;
lct.link(i+n,u);lct.link(i+n,v);
st.insert({w,i});
}else{
lct.split(u,v);
int va=lct.mn[v],id=lct.id[v]-n;
lct.cut(id+n,e[id].u);lct.cut(id+n,e[id].v);
lct.link(i+n,u),lct.link(i+n,v);
st.erase({e[id].w,id});
st.insert({w,i});
}
if(cnt==n-1)ans=min(ans,w-st.begin()->first);
}
printf("%d\n",ans);
return 0;
}
哇,代码好短,以后就跟着 lct 大哥混了,这不比树剖强?

浙公网安备 33010602011771号