kruskal

最小生成树

kruskal 是一种常见且好理解的最小生成树(MST)算法。

前置知识

生成树

在有 n 的顶点的无向图中,取其中 n-1 条边相连,所得到的树即为生成树。

最小生成树就是生成树边权和最小。

kruskal 求 MST

kruskal 基于贪心。

如果让你的选择之和最小,该怎么选?

每次选择的边权都是没选过的最小的,直到选了 n-1 条边。

但这样选有时会出问题。

如上图,选最小的边应该是:

但显然,这不是一个树。

所以在连边之前,还要判断一下两个点是否在同一个连通块内。

判联通用什么?

对,并查集!

那么整个 kruskal 的过程就是:

排序,判联通,加边。

Code(P3366)

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
int re()
{
int s=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-')f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
s=s*10+ch-48,ch=getchar();
return s*f;
}
void wr(int s)
{
if(s<0)putchar('-'),s=-s;
if(s>9)wr(s/10);
putchar(s%10+48);
}
const int inf=1e5+7;
int n,m,cnt,ans;
struct kruskal{
int from,to,val;
bool operator <(const kruskal &b)const
{
return val<b.val;
}
};
vector<kruskal>h;
int fa[inf];
int find(int x)
{
if(x==fa[x])return fa[x];
return fa[x]=find(fa[x]);
}
int main()
{
n=re();m=re();
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++)
{
int u=re(),v=re(),w=re();
h.push_back((kruskal){u,v,w});
}
sort(h.begin(),h.end());
int len=h.size();
for(int i=0;i<len;i++)
{
int r1=find(h[i].from),r2=find(h[i].to);
if(r1==r2)continue;
cnt++;ans+=h[i].val;
fa[r1]=r2;
if(cnt==n-1)break;
}
wr(ans);
return 0;
}

练习

P4826

P2212

次小生成树

前置知识:

为方便叙述,最小生成树中的 \(n-1\) 边叫做树边,剩余的 \(m-n+1\) 条边叫非树边。

非严格次小生成树

显然,对于已经生成的最小生成树来说,每一条非树边的加入,都会形成一个环。那么再将环上的树边中最大的边删除,就能得到次小生成树的一颗候选树。

令最小生成树大小为 \(minn\),新加入的非树边权值为 \(new\),环上的最大树边为 \(max\),那么候选树的大小就是 \(minn-max+new\),我们所求则是 \(min\{minn-max+new\}\)。

严格次小生成树

当 \(new=max\) 时,若按上述方法进行维护,得到的 \((minn-max+new)=minn\)。

此时,就应该选择环上树边的次大值 \(nexm\),\((minn-nexm+new)>minn\)。

解法

现在的问题就在于,如何快速求出两点间树边的最大值和次大值。

若直接用两个二维数组将两点间的最大值,次大值存下来是不现实的,\(O(n^2)\) 的空间复杂度不允许。

考虑倍增,储存下来每个节点到其 \(2^k\) 级祖先的最大值和次大值,然后在跳 lca 的过程中维护最大值和次大值。

比如,求从 u 到 v 的最大值,可以先找出 u 和 v 的 lca,然后分别求出 u 到 lca 和 v 到 lca 的最大值,两者取最大即可。

Code:

#include<cstdio>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
int re()
{
int s=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-')f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
s=s*10+ch-48,ch=getchar();
return s*f;
}
void wr(int s)
{
if(s<0)putchar('-'),s=-s;
if(s>9)wr(s/10);
putchar(s%10+48);
}
const int inf=3e5+7;
int n,m,minn,ans=1e18;
int fa[inf];
struct kruskal{
int from,to,val;
bool operator <(const kruskal &b)const
{
return val<b.val;
}
};
vector<kruskal>k;
struct edge{
int to,val;
edge(int to,int val):
to(to),val(val){}
};
vector<edge>e[inf];
bool vis[inf<<1];int cnt;
int find(int s)
{
if(s==fa[s])return s;
return fa[s]=find(fa[s]);
}
int dep[inf],fat[inf][20];
int maxn[inf][20],nexm[inf][20];
void dfs(int now,int from)
{
dep[now]=dep[from]+1;
fat[now][0]=from;
for(int i=0;i<e[now].size();i++)
{
int p=e[now][i].to;
if(p==from)continue;
maxn[p][0]=e[now][i].val;
dfs(p,now);
}
}
void swap(int &a,int &b){a^=b^=a^=b;}
int max(int a,int b){return a>b?a:b;}
int _lca(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
for(int i=19;i>=0;i--)
if(dep[fat[x][i]]>=dep[y])
x=fat[x][i];
if(x==y)return x;
for(int i=19;i>=0;i--)
if(fat[x][i]!=fat[y][i])
x=fat[x][i],y=fat[y][i];
return fat[x][0];
}
int ask(int x,int y,int z)
{
int maxi=0;
for(int i=19;i>=0;i--)
{
if(dep[fat[x][i]]>=dep[y])
{
if(maxn[x][i]==z)
maxi=max(maxi,nexm[x][i]);
else maxi=max(maxi,maxn[x][i]);
x=fat[x][i];
}
}
return maxi;
}
signed main()
{
n=re();m=re();ans+=7;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++)
{
kruskal data;
data.from=re(),data.to=re(),data.val=re();
k.push_back(data);
}
sort(k.begin(),k.end());
for(int i=0;i<m;i++)
{
int k1=k[i].from,k2=k[i].to,v=k[i].val;
int r1=find(k1),r2=find(k2);
if(r1==r2)continue;
vis[i]=1;fa[r1]=r2;
cnt++;minn+=v;
e[k1].push_back(edge(k2,v));
e[k2].push_back(edge(k1,v));
if(cnt==n-1)break;
}
dfs(1,1);
for(int i=1;i<20;i++)
{
for(int j=1;j<=n;j++)
{
fat[j][i]=fat[fat[j][i-1]][i-1];
maxn[j][i]=max(maxn[j][i-1],maxn[fat[j][i-1]][i-1]);
if(maxn[j][i-1]==maxn[fat[j][i-1]][i-1])
nexm[j][i]=max(nexm[j][i-1],nexm[fat[j][i-1]][i-1]);
else if(maxn[j][i-1]<maxn[fat[j][i-1]][i-1])
nexm[j][i]=max(maxn[j][i-1],nexm[fat[j][i-1]][i-1]);
else if(maxn[j][i-1]>maxn[fat[j][i-1]][i-1])
nexm[j][i]=max(nexm[j][i-1],maxn[fat[j][i-1]][i-1]);
}
}
for(int i=0;i<m;i++)
{
if(vis[i])continue;
int k1=k[i].from,k2=k[i].to,val=k[i].val;
int lca=_lca(k1,k2);
int max1=ask(k1,lca,val),max2=ask(k2,lca,val);
ans=min(ans,minn+val-max(max1,max2));
}
wr(ans);putchar('\n');
return 0;
}

kruskal 重构树

用途

巧妙地求解询问连接两点的所有路径中最大边的最小值或者最小边的最大值问题。

思路

kruskal 求 MST 的时候是逐步加边,而 kruskal 重构树则是将要加的边转化成点,并连接原来的两个点,边权为点权。

就像这样:

实例(图片来自 OI-Wiki)

原无向图

重构树

性质

  • kruskal 重构树是一棵二叉堆
  • 最小生成树的重构树是大根堆,最大生成树的重构树是小根堆
  • 图上两点最小路径的最大值或最大路径的最小值为重构树上两点的 lca 的点权。
  • 重构树上共 \(n\times 2-1\) 的点,其中,n 个叶节点为原来图中的节点。

例题讲解

归程

这个题可以用可持久化并查集切掉。

原谅我不会

海拔比水位高的路车可以通过,剩下的路只能步行涉水。

那么就先预处理出整张图的 kruskal 重构树,然后找到树上的一个节点 x,满足 val[x]>p&&val[fa[x]]<=p,这样,以 x 为根的子树中的叶节点就是能通过车连通的。

提前预处理出图上每个节点到 1 号节点的最短路,然后在重构树中维护每个节点为根时子树中距离 1 号节点的最小值。

至于怎么找到重构树上的节点 x,那就要用到倍增了。

坑点

  1. 最短路用 dijkstra,因为 SPFA 死了。
  2. 多测记得清空,尤其是 lastans 容易忘。

Code

#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
int re()
{
int s=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-')f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
s=s*10+ch-48,ch=getchar();
return s*f;
}
void wr(int s)
{
if(s<0)putchar('-'),s=-s;
if(s>9)wr(s/10);
putchar(s%10+48);
}
const int inf=4e5+7;
int t,n,m,q,k,s,last;
int fir[inf],nex[inf<<1],poi[inf<<1],val[inf<<1],cnt;
void ins(int x,int y,int z)
{
nex[++cnt]=fir[x];
poi[cnt]=y;
val[cnt]=z;
fir[x]=cnt;
}
int dis[inf];bool vis[inf];
struct dij{
int id,dis;
dij(int id,int dis):id(id),dis(dis){}
bool operator <(const dij &b)const
{
return dis>b.dis;
}
};
struct kruskal{
int from,to,val;
bool operator <(const kruskal &b)const
{
return val>b.val;
}
};
int fat[inf],cntm,cntn;
int find(int x)
{
if(x==fat[x])return fat[x];
return fat[x]=find(fat[x]);
}
struct SR_heap{
int lc,rc,val,minn;
#define lc(i) T[i].lc
#define rc(i) T[i].rc
};
SR_heap T[inf];
int fa[inf][20];
void dfs(int now,int from)
{
fa[now][0]=from;
if(lc(now)==0)
{
T[now].minn=dis[now];
return;
}
dfs(lc(now),now);
dfs(rc(now),now);
T[now].minn=min(T[lc(now)].minn,T[rc(now)].minn);
}
int tiao(int x,int k)
{
for(int i=19;i>=0;i--)
{
if(T[fa[x][i]].val<=k)continue;
x=fa[x][i];
}
return x;
}
int main()
{
t=re();
while(t--)
{
cntn=n=re(),m=re();
cnt=cntm=last=0;
memset(fir,0,sizeof(fir));
memset(nex,0,sizeof(nex));
memset(poi,0,sizeof(poi));
memset(val,0,sizeof(val));
memset(dis,127,sizeof(dis));
memset(vis,0,sizeof(vis));
memset(fa,0,sizeof(fa));
memset(T,0,sizeof(T));
priority_queue<dij>heap;
vector<kruskal>h;
for(int i=1;i<=m;i++)
{
int u=re(),v=re(),l=re(),a=re();
ins(u,v,l),ins(v,u,l);
h.push_back((kruskal){u,v,a});
}
heap.push(dij(1,0));dis[1]=0;
while(heap.size())
{
dij now=heap.top();heap.pop();
if(vis[now.id])continue;
vis[now.id]=1;
for(int i=fir[now.id];i;i=nex[i])
{
int p=poi[i];
if(dis[p]>dis[now.id]+val[i])
{
dis[p]=dis[now.id]+val[i];
heap.push(dij(p,dis[p]));
}
}
}
for(int i=1;i<=n;i++)fat[i]=i;
sort(h.begin(),h.end());
int len=h.size();
for(int i=0;i<len;i++)
{
int r1=find(h[i].from),r2=find(h[i].to);
if(r1==r2)continue;
cntm++;cntn++;
fat[r1]=fat[r2]=fat[cntn]=cntn;
T[cntn]=(SR_heap){r1,r2,h[i].val};
}
for(int i=1;i<=cntn;i++)
fat[i]=find(i);
dfs(fat[1],fat[1]);
for(int j=1;j<20;j++)
for(int i=1;i<=cntn;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
q=re(),k=re(),s=re();
for(int i=1;i<=q;i++)
{
int v=re(),p=re();
v=(v+k*last-1)%n+1;
p=(p+k*last)%(s+1);
wr(last=T[tiao(v,p)].minn);putchar('\n');
}
}
return 0;
}

练习

P1967

P4197

kruskal 相关题目

kruskal 及其应用的更多相关文章

  1. 图的生成树(森林)(克鲁斯卡尔Kruskal算法和普里姆Prim算法)、以及并查集的使用

    图的连通性问题:无向图的连通分量和生成树,所有顶点均由边连接在一起,但不存在回路的图. 设图 G=(V, E) 是个连通图,当从图任一顶点出发遍历图G 时,将边集 E(G) 分成两个集合 T(G) 和 ...

  2. 最小生成树---Prim算法和Kruskal算法

    Prim算法 1.概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (gra ...

  3. 最小生成树(prim&kruskal)

    最近都是图,为了防止几次记不住,先把自己理解的写下来,有问题继续改.先把算法过程记下来: prime算法:                  原始的加权连通图——————D被选作起点,选与之相连的权值 ...

  4. Kruskal 最小生成树算法

    对于一个给定的连通的无向图 G = (V, E),希望找到一个无回路的子集 T,T 是 E 的子集,它连接了所有的顶点,且其权值之和为最小. 因为 T 无回路且连接所有的顶点,所以它必然是一棵树,称为 ...

  5. 权重最小生成树的思想与Kruskal算法

    晚上做携程的笔试题,附加题考到了权重最小生成树.OMG,就在开考之前,我还又看过一遍这内容,可因为时间太紧,也从来没有写过代码,就GG了.又吃了眼高手低的亏.这不,就好好总结一下,亡羊补牢. 权重最小 ...

  6. 最小生成树 kruskal算法 codevs 1638 修复公路

    1638 修复公路  时间限制: 1 s  空间限制: 256000 KB  题目等级 : 钻石 Diamond 题解       题目描述 Description A地区在地震过后,连接所有村庄的公 ...

  7. 洛谷P1991无线通讯网[kruskal | 二分答案 并查集]

    题目描述 国防部计划用无线网络连接若干个边防哨所.2 种不同的通讯技术用来搭建无线网络: 每个边防哨所都要配备无线电收发器:有一些哨所还可以增配卫星电话. 任意两个配备了一条卫星电话线路的哨所(两边都 ...

  8. NOIP2013货车运输[lca&&kruskal]

    题目描述 A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多 ...

  9. 最小生成树的Kruskal算法实现

    最近在复习数据结构,所以想起了之前做的一个最小生成树算法.用Kruskal算法实现的,结合堆排序可以复习回顾数据结构.现在写出来与大家分享. 最小生成树算法思想:书上说的是在一给定的无向图G = (V ...

  10. poj2485 kruskal与prim

    Kruskal: #include<iostream> #include<cstdio> #include<algorithm> using namespace s ...

随机推荐

  1. SSM整合_年轻人的第一个增删改查_查找

    写在前面 SSM整合_年轻人的第一个增删改查_基础环境搭建 SSM整合_年轻人的第一个增删改查_查找 SSM整合_年轻人的第一个增删改查_新增 SSM整合_年轻人的第一个增删改查_修改 SSM整合_年 ...

  2. python相关知识理解

    Python3 基础了解 编码  Python 3 源码文件以 UTF-8 编码,所有字符串都是 unicode 字符串   # -*- coding: cp-1252 -*- 标识符 · 第一个字符 ...

  3. ArcGIS建筑物简化和建筑物群聚合算法实验

    一.下载OSM数据 首先从OpenStreetMap官网下载我们需要的实验数据,这里我选择清华和北大校园作为本次实验数据 二.数据处理 将我们下载的实验数据导入ArcGIS.由于OSM数据是.osm格 ...

  4. Linux ubuntu下docker容器安装和基础命令

    Docker介绍: 云计算就好比大货轮,docker就是集装箱虚拟机虽然可以隔离出很多"子电脑",但占用空间更大,启动更慢,虚拟机软件可能还要花钱(例如VMWare). 而容器技术 ...

  5. 树莓派开发笔记(十二):入手研华ADVANTECH工控树莓派UNO-220套件(一):介绍和运行系统

    前言   树莓派也可以做商业应用,工业控制,其稳定性和可靠性已经得到了验证,故而工业控制,一些停车场等场景也有采用树莓派作为主控的,本片介绍了研华ADVANTECH的树莓派套件组UNO-220-P4N ...

  6. Runable与Callable的区别

    Runable与Callable的区别: public interface Callable<V> { V call() throws Exception;//V是Callable返回值的 ...

  7. XCTF练习题---WEB---disabled_button

    XCTF练习题---WEB---disabled_button flag:cyberpeace{74bcfce0746d18dd8d560e0f0529a8cf} 解题步骤: 1.观察题目,打开场景 ...

  8. ChCore Lab4 多核处理 实验笔记

    本文为上海交大 ipads 研究所陈海波老师等人所著的<现代操作系统:原理与实现>的课程实验(LAB)的学习笔记的第四篇:多核处理.所有章节的笔记可在此处查看:chcore | 康宇PL' ...

  9. GitStats - 统计Git所有提交记录工具

    如果你是研发效能组的一员或者在从事 CI/CD 或 DevOps,除了提供基础设施,指标和数据是也是一个很重要的一环,比如需要分析下某个 Git 仓库代码提交情况: 该仓库的代码谁提交的代码最多 该仓 ...

  10. C#/VB.NET 在Excel单元格中应用多种字体格式

    在Excel中,可对单元格中的字符串设置多种不同样式,通常只需要获取到单元格直接设置样式即可,该方法设置的样式会应用于该单元格中的所有字符.如果需要对单元格中某些字符设置样式,则可以参考本文中的方法. ...