最小生成树


最小生成树

树是一种特殊的图,不存在环。因长得像现实生活中的树,因此得名。

最小生成树就是在一个图中去掉一些边,使图变成树之后的总边权最小。

最小生成树的算法有很多,例如:

  • Kruskal
  • Prim
  • Boruvka

生成树题单

先介绍一道模板题:P2212

Kruskal

思路:将所有的边权从小到大排序,然后按权加边。但如果两个点已经在一个连通块内就不再连边。

核心:并查集 。

处理边权:

int work(int a,int b)
{
	int Ax=dt[a].x-dt[b].x;
	int Ay=dt[a].y-dt[b].y;
	return Ax*Ax+Ay*Ay;
}

初始化:

for(int i=1;i<=n;i++)fa[i]=i;

找根:

int find(int x){
	if(x==fa[x])return fa[x];
	return fa[x]=find(fa[x]);
}

合并:

for(int i=1;i<=cnt;i++)
{
	int rootf=find(e[i].from),
		roott=find(e[i].to);
	if(rootf==roott)continue;
	num++;
	fa[roott]=rootf;
	ans+=e[i].val;
	if(num==n-1)break;
}

最后再判断一共加进去了多少边。如果 \( 说明图不连通。

时间复杂度为 \(O(m\log m)\),适用于稀疏图。

Prim

不同于 KruskalPrim 的基本思想是加点。

有点像 Dijkstra,每次选择距离最小的点,再用这个点维护到其他点的距离。

邻接矩阵存图:

for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
		if(i==j){
			val[i][j]=0;
			continue;
		}
		int vl=work(i,j);
		if(vl

然后跑一边 bfs 判一手是否联通:

bool bfs(int s)
{
	h.push(s);
	vis[s]=1;
	while(h.size())
	{
		int now=h.front();
		h.pop();
		for(int i=1;i<=n;i++)
		{
			if(vis[i]||val[now][i]==2147483647||now==i)
				continue;
			h.push(i);
			vis[i]=1;
		}
	}
	for(int i=1;i<=n;i++)
		if(vis[i]==0)return 1;
	return 0;
}

初始化:假设 1 已经在这个最小生成树里。那么对于每个点,均未加入树中,dis 即为 1 到这个点的距离。

然后暴力找现在点权值最小的点,加入到树中,并更新点权值。

for(int i=1;iminn)
            continue;
        p=j;minn=dis[j];
    }
    if(!p)continue;
    ans+=dis[p];
    vis[p]=1;
    for(int j=1;j<=n;j++)
    {
        if(vis[j]||dis[j]

时间复杂度 \(O(n^2)\),适用于稠密图 特别是完全图 的最小生成树问题。

Dijkstra 一样, Prim 也是可以堆优化的,时间复杂度 \(O(n\log n)\),不过由于 STL 常数巨大,堆优 prim 并不能 AC 此题。

Code(Kruskal):

#include
#include
using namespace std;
int n,c,cnt,ans,num;
int fa[2010];
struct dot{
	int x,y;
}dt[2010];
struct edge{
	int from,to,val;
}e[4000010];
int dododo(int i,int j){
	int Ax=dt[i].x-dt[j].x,
		Ay=dt[i].y-dt[j].y;
	return Ax*Ax+Ay*Ay;
}
bool cmp(edge x,edge y){
	return x.val

Code:(Prim)

#include
#include
#include
using namespace std;
struct node{
    int x,y;
}dt[2010];
int c,n,ans;
int val[2010][2010],vis[2010],dis[2010];
queueh;bool v[2010];
int work(int a,int b)
{
    int Ax=dt[a].x-dt[b].x;
    int Ay=dt[a].y-dt[b].y;
    return Ax*Ax+Ay*Ay;
}
bool bfs(int s)
{
    h.push(s);
    v[s]=1;
    while(h.size())
    {
        int now=h.front();
        h.pop();
        for(int i=1;i<=n;i++)
        {
            if(v[i]||val[now][i]==2147483647||now==i)
                continue;
            h.push(i);
            v[i]=1;
        }
    }
    for(int i=1;i<=n;i++)
        if(v[i]==0)return 1;
    return 0;
}
int main()
{
    std::ios::sync_with_stdio(false);
    cin>>n>>c;
    for(int i=1;i<=n;i++)
        cin>>dt[i].x>>dt[i].y;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i==j){
                val[i][j]=0;
                continue;
            }
            int vl=work(i,j);
            if(vlminn)
                continue;
            p=j;minn=dis[j];
        }
        if(!p)continue;
        ans+=dis[p];
        vis[p]=1;
        for(int j=1;j<=n;j++)
        {
            if(vis[j]||dis[j]

严格次小生成树

模板

前置知识:

  • kruskal

  • 倍增求 lca

为方便叙述,最小生成树中的 \(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\)

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

若直接用两个二维数组直接将最大值,次大值存下来是不现实的,空间不允许。

用倍增的思想,可以存下来每个节点到其 \(2^k\) 级祖先的最大值和次大值。

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

Code:

#include
#include
#include
#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 valk;
struct edge{
	int to,val;
	edge(int to,int val):
		to(to),val(val){}
};
vectore[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;ib?a:b;}
int _lca(int x,int y)
{
	if(dep[x]=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;imaxn[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