最小生成树
最小生成树
树是一种特殊的图,不存在环。因长得像现实生活中的树,因此得名。
最小生成树就是在一个图中去掉一些边,使图变成树之后的总边权最小。
最小生成树的算法有很多,例如:
- 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
不同于 Kruskal,Prim 的基本思想是加点。
有点像 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