【学习笔记】基础算法——生成树

最小生成树

应该都知道最小生成树是什么吧。

不知道的左转先理解最小生成树的定义!

好的上例题!

水。

两种做法,一个是 K 啥的,还有一个是 Prim 吧。

K 算法

时间复杂度 \(O(m\log m)\),瓶颈在于排序。

非常常用!代码很短!但是适用于稀疏图。

代码不贴了。

P 算法

时间复杂度应该是 \(O(n^2)\) 吧。

K 算法是加边,而 P 算法是加点。

很简单。适用于稠密图。但是并不很常用,因为一般 K 算法都是首选。

最小生成树的运用

我问你呢,有多少个题目会在脑袋上写着大大的“最小生成树”五个大字?不,不可能。都是藏着掖着的!

把“最小生成树”的提示藏在字里行间,需要你仔细剖析题目意思,详细分析题目要求才能发现。

运用很巧妙哇!

直说不好说啊。看几个题吧。

例题——Dungeons and Candies

乍一看,你看得出来这是最小生成树?不,当然看不出来。

它是藏着的!你需要去剖析,它的这个结构确实是一个树的形态,然后整个你需要自己去枚举,去连边,去造生成树。最后输出的时候还得遍历!

还是有难度的。

来,看一下代码:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 1e3+5 , M = 2e6+5;
struct line{int u,v,w;}ln[M];vector<int> g[N];
char c[N][15][15];int n,m,k,w,fa[N],cnt,ans[N],sum;
bool cmp(line l1,line l2){return l1.w<l2.w;}
int FF(int u){return (fa[u]==u?u:fa[u]=FF(fa[u]));}
bool Can_It_Merge(int u,int v){return (FF(u)!=FF(v));}
void Merge(int u,int v){if(FF(u)!=FF(v))fa[FF(v)]=FF(u);return;}
void DFS(int u,int Fat){
    ans[u]=Fat;if(u)cout<<u<<" "<<Fat<<"\n";
    for(int v:g[u])if(v^Fat)DFS(v,u);return;
}
int main(){
    cin>>n>>m>>k>>w;
    for(int x=1;x<=k;x++)for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)cin>>c[x][i][j];
    for(int x=1;x<=k;x++)ln[++cnt]={0,x,n*m};
    for(int x=1;x<k;x++)for(int y=x+1;y<=k;y++){
        int tmp=0;
        for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)
            tmp+=(c[x][i][j]!=c[y][i][j]);
        ln[++cnt]={x,y,tmp*w};
    }
    sort(ln+1,ln+cnt+1,cmp);for(int x=1;x<=k;x++)fa[x]=x;
    for(int i=1;i<=cnt;i++){
        if(!Can_It_Merge(ln[i].u,ln[i].v))continue;
        Merge(ln[i].u,ln[i].v),sum+=ln[i].w;
        g[ln[i].u].push_back(ln[i].v),g[ln[i].v].push_back(ln[i].u);
    }
    cout<<sum<<"\n";DFS(0,-1);
    return 0;
}

例题——Daleks' Invasion (easy)

这个倒是一眼看出最小生成树。但是整个后面的思路需要一系列的推导!

你需要推式子,然后要理解最小生成树的本质,才能知道我们最终的那个答案究竟是啥。最后还要记得判,叫啥呢,无解。

很难吧。

上代码!

#include<bits/stdc++.h>
#define LL long long
#define fr first
#define se second
#define pb push_back
using namespace std;
const int N = 1e5+5 , M = 1e6+5;
struct line{int u,v,w;}ln[M];
int n,m,fa[N],num;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
bool cmp(line l1,line l2){return l1.w<l2.w;}
int FF(int u){return (fa[u]==u?u:fa[u]=FF(fa[u]));}
void Merge(int u,int v){if(FF(u)!=FF(v))fa[FF(u)]=FF(v);return;}
int main(){
    n=read(),m=read();
    for(int i=1;i<=m;i++){int x,y,z;cin>>x>>y>>z;ln[i]={x,y,z};}
    sort(ln+2,ln+m+1,cmp);for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=2;i<=m;i++){
        Merge(ln[i].u,ln[i].v);++num;
        if(FF(ln[1].u)==FF(ln[1].v)){cout<<ln[i].w<<"\n";return 0;}
    }
    if(num<n-1)cout<<"1000000000\n";
    return 0;
}

例题——Omg Graph

这个的话,你在构建最小生成树的同时需要维护很多东西,然后才能计算出最终答案,有难度!

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 2e5+5;
const LL Inf = 0x3f3f3f3f3f3f3f3f;
struct line{LL u,v,w;}ln[N];
LL T,n,m,ans,fa[N],mx[N],mn[N];
bool cmp(line l1,line l2){return l1.w<l2.w;}
int FF(int u){return (fa[u]==u?u:fa[u]=FF(fa[u]));}
void Merge(int u,int v,LL K){
    int fu=FF(u),fv=FF(v);if(fu==fv)return;
    fa[fv]=fu;mx[fu]=max({mx[fu],mx[fv],K});
    mn[fu]=min({mn[fu],mn[fv],K});return;
}
int main(){
    cin>>T;
    while(T--){
        cin>>n>>m;
        for(int i=1;i<=m;i++)cin>>ln[i].u>>ln[i].v>>ln[i].w;
        sort(ln+1,ln+m+1,cmp);ans=Inf;
        for(int i=1;i<=n;i++)fa[i]=i,mx[i]=-Inf,mn[i]=Inf;
        for(int i=1;i<=m;i++){
            Merge(ln[i].u,ln[i].v,ln[i].w);
            if(FF(1)==FF(n))ans=min(ans,mx[FF(1)]+mn[FF(1)]);
        }
        cout<<ans<<"\n";
    }
    return 0;
}

总结

生成树(为啥不叫最小生成树?因为也有最大生成树啊喂!)的运用很广,一般都是需要去挖掘生成树其本质,有时还需要维护更多东西。直白告诉你让你生成一棵生成树的题那是少之又少。

所以想要拿下生成树的所有题目,第一步要学会从题目中挖掘出生成树的影子,第二步要学会分析这个生成树具体都要维护些什么,第三步要能确保你的算法准确无误没有瑕疵,第四步才是上手写代码!

posted @ 2025-07-19 11:50  嘎嘎喵  阅读(21)  评论(1)    收藏  举报