Luogu P4234 最小差值生成树
题意
给定一个 \(n\) 个点 \(m\) 条边的有权无向图,求出原图的一棵生成树使得该树上最大边权与最小边权的差值最小。
\(\texttt{Data Range:}1\leq n\leq 5\times 10^4,1\leq m\leq 2\times 10^5,1\leq w\leq 5\times 10^4\)
题解
这位傻逼把 \(n\) 个节点的树所含有的边的数目认为是 \(n\),还在出现环的时候立即统计答案(可能生成树都没构建出来),导致 \(\texttt{WA}\) 了一发又一发,请大家不要学他。
直接进入正题。
考虑先将边权从小到大排序,然后一条一条的考虑。
如果这条边加入当前的图中不会成环的话,那么直接加进来即可。
如果成了环的话,考虑在环上删去边权最小的边。
为什么这么做是正确的呢?
假设我们现在考虑的是第 \(k\) 条边,连接 \(u_i\) 和 \(v_i\),边权为 \(w_i\),讨论一下当前图中边权最小的边的位置。
当这条边的位置在环上的时候,由于新图的边权最小值改变了,所以需要更新答案。
当这条边的位置不在环上的时候,对答案无影响。
所以我们每一次需要更新答案的时候我们都进行了更新,自然答案就是对的了。
加入完这条边之后,如果当前的图是原图的一棵生成树的话那么更新一下答案就好了。
注意到整个过程涉及到删边和连边操作,所以采用 \(\texttt{LCT}\) 来维护生成树。
更新答案时,由于要找到最小边权的边的位置,并且每一次加完边之后最小边权的边的位置单调不降(因为排过序了),打标记+移动指针就可以了。
代码
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef long long int li;
const ll MAXN=3e5+51;
struct Edge{
ll from,to,dist;
inline bool operator <(const Edge &rhs)const
{
return dist<rhs.dist;
}
};
ll n,m,ptr=1,cur,x,y,offset=60000,res;
Edge ed[MAXN];
ll ffa[MAXN],vis[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline ll find(ll x)
{
return x==ffa[x]?x:ffa[x]=find(ffa[x]);
}
namespace LCT{
struct Node{
ll fa,mn,val,rv,sz;
ll ch[2];
};
struct LinkCutTree{
Node nd[MAXN];
ll st[MAXN];
#define ls nd[x].ch[0]
#define rs nd[x].ch[1]
inline bool nroot(ll x)
{
return nd[nd[x].fa].ch[0]==x||nd[nd[x].fa].ch[1]==x;
}
inline ll get(ll x,ll y)
{
return nd[x].val<nd[y].val?x:y;
}
inline void update(ll x)
{
nd[x].mn=get(x,get(nd[ls].mn,nd[rs].mn));
}
inline void reverse(ll x)
{
swap(ls,rs),nd[x].rv^=1;
}
inline void spread(ll x)
{
if(nd[x].rv)
{
ls?reverse(ls):(void)(1),rs?reverse(rs):(void)(1);
nd[x].rv=0;
}
}
inline void rotate(ll x)
{
ll fa=nd[x].fa,gfa=nd[fa].fa;
ll dir=nd[fa].ch[1]==x,son=nd[x].ch[!dir];
if(nroot(fa))
{
nd[gfa].ch[nd[gfa].ch[1]==fa]=x;
}
nd[x].ch[!dir]=fa,nd[fa].ch[dir]=son;
if(son)
{
nd[son].fa=fa;
}
nd[fa].fa=x,nd[x].fa=gfa,update(fa);
}
inline void splay(ll x)
{
ll fa=x,gfa,cur=0;
st[++cur]=fa;
while(nroot(fa))
{
st[++cur]=fa=nd[fa].fa;
}
while(cur)
{
spread(st[cur--]);
}
while(nroot(x))
{
fa=nd[x].fa,gfa=nd[fa].fa;
if(nroot(fa))
{
rotate((nd[fa].ch[0]==x)^(nd[gfa].ch[0]==fa)?x:fa);
}
rotate(x);
}
update(x);
}
inline void access(ll x)
{
for(register int i=0;x;x=nd[i=x].fa)
{
splay(x),rs=i,update(x);
}
}
inline void makeRoot(ll x)
{
access(x),splay(x),reverse(x);
}
inline void split(ll x,ll y)
{
makeRoot(x),access(y),splay(y);
}
inline void link(ll x)
{
makeRoot(ed[x].from);
nd[nd[ed[x].from].fa=x+offset].fa=ed[x].to;
nd[x+offset].val=ed[x].dist,update(x);
}
inline void cut(ll x)
{
access(ed[x-offset].from),splay(x);
ls=rs=nd[ls].fa=nd[rs].fa=0,update(x);
}
#undef ls
#undef rs
};
}
LCT::LinkCutTree lct;
int main()
{
n=read(),m=read();
for(register int i=1;i<=m;i++)
{
ed[i].from=read(),ed[i].to=read(),ed[i].dist=read();
}
for(register int i=0;i<=n;i++)
{
ffa[i]=i,lct.nd[i].val=0x7fffffff;
}
sort(ed+1,ed+m+1);
for(register int i=1;i<=m;i++)
{
if(find(x=ed[i].from)!=find(y=ed[i].to))
{
vis[i]=1,lct.link(i),cur++,ffa[ffa[y]]=ffa[x];
if(cur==n-1)
{
res=ed[i].dist-ed[ptr].dist;
}
}
else
{
if(x==y)
{
continue;
}
lct.split(x,y),vis[lct.nd[y].mn-offset]=0;
lct.cut(lct.nd[y].mn),lct.link(i),vis[i]=1;
while(!vis[ptr])
{
ptr++;
}
if(cur==n-1)
{
res=min(res,ed[i].dist-ed[ptr].dist);
}
}
}
printf("%d\n",res);
}
Luogu P4234 最小差值生成树的更多相关文章
- P4234 最小差值生成树
题目 P4234 最小差值生成树 做法 和这题解法差不多,稍微变了一点,还不懂就直接看代码吧 \(update(2019.2):\)还是具体说一下吧,排序,直接加入,到了成环情况下,显然我们要把此边代 ...
- 【Luogu】P4234最小差值生成树(LCT)
题目链接 能把LCT打得每个函数都恰有一个错误也是挺令我惊讶的. 本题使用LCT维护生成树,具体做法是对原图中的每个边建一个点,然后连边的时候相当于是将边的起点跟“边”这个点连起来,边的终点也跟它连起 ...
- 【刷题】洛谷 P4234 最小差值生成树
题目描述 给定一个标号为从 \(1\) 到 \(n\) 的.有 \(m\) 条边的无向图,求边权最大值与最小值的差值最小的生成树. 输入输出格式 输入格式: 第一行两个数 \(n, m\) ,表示图的 ...
- 洛谷P4234 最小差值生成树(lct动态维护最小生成树)
题目描述 给定一个标号为从 11 到 nn 的.有 mm 条边的无向图,求边权最大值与最小值的差值最小的生成树. 输入输出格式 输入格式: 第一行两个数 n, mn,m ,表示图的点和边的数量. ...
- P4234 最小差值生成树 LCT维护边权
\(\color{#0066ff}{ 题目描述 }\) 给定一个标号为从 \(1\) 到 \(n\) 的.有 \(m\) 条边的无向图,求边权最大值与最小值的差值最小的生成树. \(\color{#0 ...
- [洛谷P4234] 最小差值生成树
题目类型:\(LCT\)动态维护最小生成树 传送门:>Here< 题意:求一棵生成树,其最大边权减最小边权最小 解题思路 和魔法森林非常像.先对所有边进行排序,每次加边的时候删除环上的最小 ...
- 洛谷 P4234 最小差值生成树(LCT)
题面 luogu 题解 LCT 动态树Link-cut tree(LCT)总结 考虑先按边权排序,从小到大加边 如果构成一颗树了,就更新答案 当加入一条边,会形成环. 贪心地想,我们要最大边权-最小边 ...
- Luogu 4234 最小差值生成树 - LCT 维护链信息
Solution 将边从小到大排序, 添新边$(u, v)$时 若$u,v$不连通则直接添, 若连通则 把链上最小的边去掉 再添边. 若已经加入了 $N - 1$条边则更新答案. Code #incl ...
- 洛谷P4234 最小差值生成树(LCT,生成树)
洛谷题目传送门 和魔法森林有点像,都是动态维护最小生成树(可参考一下Blog的LCT总结相关部分) 至于从小到大还是从大到小当然无所谓啦,我是从小到大排序,每次枚举边,还没连通就连,已连通就替换环上最 ...
随机推荐
- 多线程循环打印数组 -- Java笔记
问题描述: 现有多个长度相同的数组,现要求使用多线程将数组内的数交替打印. 如: int[] ai = {1,2,3,4,5,6,7}; String[] ac = {"A",&q ...
- Python实现的数据结构与算法之队列详解
本文实例讲述了Python实现的数据结构与算法之队列.分享给大家供大家参考.具体分析如下: 一.概述 队列(Queue)是一种先进先出(FIFO)的线性数据结构,插入操作在队尾(rear)进行,删除操 ...
- Recursive sequence (矩阵快速幂)2016ACM/ICPC亚洲区沈阳站
题目 Farmer John likes to play mathematics games with his N cows. Recently, they are attracted by recu ...
- ubuntu 18.04 搭建flask服务器(大合集,个人实操)
ubuntu 18.04 搭建flask服务器(大合集) Ubuntu python flask 服务器 本次使用的Ubuntu版本为:Ubuntu 18.04.5 LTS (GNU/Linux 4. ...
- ansible-playbook定义变量与使用
1. ansible-playbook变量定义与使用 命令行 在Inventory中定义 在Playbook中定义 在Role中定义 注册变量(register) 系统信息变量(facts) 2. 在 ...
- 网络编程—【自己动手】用C语言写一个基于服务器和客户端(TCP)!
如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西--socket(套接字). socket(套接字):简单来讲,socket就是用于描述IP地 ...
- JS获取指定月份的天数几种方法
最近看到一个有意思的试题,正好在需求中也碰到类似的问题,即计算某个月的天数问题.碰到类似问题也许大部分会想是不是还要判断闰年.平年,如果这样想的话就复杂了,下面给出具体的计算方法. 获取月份天数方法一 ...
- nginx优化:worker_processes/worker_connections/worker_rlimit_nofile
一,优化nginx的worker进程数 1,worker_processes应设置为多少? worker_processes 4; 如何设置这个值: worker_processes默认值是1,一般要 ...
- Dokuwiki安装教程
一. CentOS设置 1. 更换阿里源 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos ...
- Linux用户和组管理命令-切换用户su
切换用户或以其他用户身份执行命令 su: 即 switch user,命令可以切换用户身份,并且以指定用户的身份执行命令 格式: su [options...] [-] [user [args...] ...