1. 程式人生 > >最小生成樹例題及其總結

最小生成樹例題及其總結

關於最小生成樹的問題差不多有如下幾個:

1.模板(加一點修改,比如給的所有邊的總邊權-最小生成樹的總邊權或者有些邊已經建好,求其他的)如第一題,第三題。第四題也差不多,只是判斷了特殊點在不在同一棵樹中
2.虛點(可以建立一個虛點,然後將其他點與他相連)
3.將給的邊先存下來,然後再反過來做(稱為時光倒流)
4.判斷是不是聯通的,或者輸出最大邊權。如第三題

題目分析:

這道題直接把所有邊權的總和記錄下來,然後做一遍最小生成樹,最後總邊權和減去最小生成樹中的邊權和就是答案。

#include<iostream>
#include<cstdio>
#include<cstring> #include<algorithm> #include<cmath> using namespace std; struct arr{ int u,v,w; }w[100000]; int n,m,fa[1000]; long long sum,ans; inline int read() { int x=0,w=1;char ch=0; while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar(); if(ch=='-') w=-1,ch=getchar(); while
(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar(); return x*w; } inline int cmp(arr a,arr b){ return a.w<b.w;} int gf(int x){return x==fa[x]?x:fa[x]=gf(fa[x]);} inline void union1(int a,int b,int c){ int f1=gf(a),f2=gf(b); if(f1!=f2){ fa[f1]=f2; ans+=c; } } int
main(){ n=read();m=read(); for(register int i=1;i<=n;++i) fa[i]=i; for(register int i=1;i<=m;++i){ w[i].u=read(),w[i].v=read(),w[i].w=read();sum+=w[i].w;} sort(1+w,1+w+m,cmp); for(register int i=1;i<=m;++i) union1(w[i].u,w[i].v,w[i].w); printf("%lld\n",sum-ans); return 0; }

題目描述:

注意這題為多組輸入輸出!!!(一開始自己沒有注意到,就老是wa,後面上網搜了下之後才曉得的)
題目意思:有一些邊已經建好了,要你在此基礎上再去求一遍最小生成樹
主要是為了練一下prim,沒什麼好講的,直接上程式碼了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using  namespace std;
int n,a[110][110],pos,q,u,v;
int vis[110],dist[110];
long long ans=0,sum;
void Prim() {
    memset(vis,0,sizeof(vis));
    for(int i=1; i<=n; i++) dist[i]=a[1][i];
    vis[1]=1;
    int pos;
    for(register int i=1; i<=n-1; ++i) {
        int minn=0x3f3f3f3f;
        for(register int j=1; j<=n; ++j) {
            if(!vis[j]&&dist[j]<minn) minn=dist[j],pos=j;
        }
        vis[pos]=1;sum+=dist[pos];
        for(register int j=1; j<=n; ++j) {
            if(!vis[j]&&dist[j]>a[pos][j])
                dist[j]=min(dist[j],a[pos][j]);
        }
    }
}
int main(){
    while(~scanf("%d",&n)){
        sum=0;
        for(int i=1;i<=n;i++) 
        for(int j=1;j<=n;++j)
            scanf("%d",&a[i][j]);
        scanf("%d",&q);
        for(int i=1;i<=q;++i) {
            scanf("%d%d",&u,&v);
            a[u][v]=0;a[v][u]=0;
        }
        Prim();
        printf("%d",sum);
    }
    return 0;
}

題目分析:

我們可以直接做最小生成樹,然後輸出最大的那條邊的權值是多少即可。題目中還要求判斷一下是否聯通,然後本蒟就想了一個辦法,把fa陣列掃一遍,如果有不同的父親,那麼就輸出-1,表示所有的邊都連完後還是不連通。但是隻得了90分。後面在網上找了一下題解,有一篇是這麼做的。(程式中備註了)

下面是90分的程式

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
struct arr{
    int u,v,w;
}w[200000];
int n,m;
long long ans;
int fa[2000];
inline int read(){
    int x=0,w=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
    return x*w; 
}
inline int cmp(arr a,arr b){ return a.w<b.w; }
inline int gf(int x){ int u=x,c; while(x!=fa[x]) x=fa[x]; while(u!=x) { c=fa[u]; fa[u]=x; u=c; } return x; }
inline void union1(int a,int b,int c) {
    int f1=gf(a),f2=gf(b);
    if(f1!=f2) { fa[f1]=f2; ans=c; }
}
int main(){
    n=read();m=read();
    for(register int i=1;i<=n;++i) fa[i]=i;
    for(register int i=1;i<=m;++i) { w[i].u=read();w[i].v=read();w[i].w=read(); }
    sort(1+w,1+w+m,cmp);
    for(register int i=1;i<=m;++i) { union1(w[i].u,w[i].v,w[i].w); }
    for(register int i=2;i<=n;++i) 
        if(fa[i]!=fa[i-1]) {printf("-1");return 0;}
    printf("%lld",ans);
    return 0; 
}

下面給出100分的程式

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
struct arr{
    int u,v,w;
}w[200000];
int n,m,num;
long long ans;
int fa[2000];
inline int read(){
    int x=0,w=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
    return x*w; 
}
inline int cmp(arr a,arr b){ return a.w<b.w; }
inline int gf(int x){ int u=x,c; while(x!=fa[x]) x=fa[x]; while(u!=x) { c=fa[u]; fa[u]=x; u=c; } return x; }
inline void union1(int a,int b,int c) {
    int f1=gf(a),f2=gf(b);
    if(f1!=f2) { fa[f1]=f2; ++num;ans=c; }
}
int main(){
    n=read();m=read();
    for(register int i=1;i<=n;++i) fa[i]=i;
    for(register int i=1;i<=m;++i) { w[i].u=read();w[i].v=read();w[i].w=read(); }
    sort(1+w,1+w+m,cmp);
    for(register int i=1;i<=m;++i) { union1(w[i].u,w[i].v,w[i].w); }
    if(num<n-1) printf("-1");//這就是網上大佬的判斷,很迷,為什麼我的不能過
    else printf("%lld",ans);
    return 0; 
}

題目分析:

這道題也差不多是最小生成樹裸題,只是在合併的時候判斷一下,最後用原本的總邊權減去生成樹中的邊權即可

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int n,m;
long long ans;
int vis[100010],fa[100010];
struct arr{
    int u,v,w;
}bot[100010];
inline int read(){
    int x=0,w=1;char ch=0;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='0') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
    return x*w;
}
int gf(int x){ return x==fa[x]?x:fa[x]=gf(fa[x]);}
inline int cmp(arr a,arr b){ return a.w>b.w;}
int main(){
    n=read();m=read();
    for(int i=1;i<=m;i++){ int x=read();vis[x]=1; }
    for(int i=1;i<n;i++){ int u=read(),v=read(),w=read(); bot[i].u=u;bot[i].v=v;bot[i].w=w; ans+=w; }
    sort(bot+1,bot+n,cmp);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<n;i++){
        int f1=gf(bot[i].u);
        int f2=gf(bot[i].v);
        if(!(vis[f1]&&vis[f2])){//不能在同一棵樹中
            fa[f2]=f1;
            vis[f1]=(vis[f1]||vis[f2]);//判斷特殊點是否在同一個集合中,應題目要求,不能在同一棵樹中
            ans-=bot[i].w;
        }
    }
    printf("%lld\n",ans);
}

這道題比上面那道題稍微複雜一些(用到了時光倒流的思想),不過也差不多,直接上程式碼吧。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

struct p{
    int x,y,ff;
}bot[400001];
int n,m,k,cnt;
int f[400001],h[400001],fire[400001],tot[400001];
bool vis[400001];
inline int read(){
    int x=0,w=1;char ch=0;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='0') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
    return x*w;
}
void add(int x,int y){ bot[++cnt].ff=x; bot[cnt].x=h[x]; bot[cnt].y=y; h[x]=cnt;}
int find(int x) { if(x!=f[x]) f[x]=find(f[x]); return f[x]; }
int main(){
    n=read();m=read();
    for(int i=1;i<=m;i++){ int x=read(),y=read(); add(x,y); add(y,x); }
    k=read();
    for(int i=1;i<=k;i++){ int x=read(); vis[x]=1,fire[i]=x; }
    for(int i=0;i<n;i++) f[i]=i;
    int block=n-k,kk=0;
    for(int i=1;i<=cnt;i++)
      if(!vis[bot[i].ff]&&!vis[bot[i].y]&&find(bot[i].ff)!=find(bot[i].y)){
          block--;
          f[find(bot[i].ff)]=find(bot[i].y);
      }//把能夠連上的並且在這兒之前不在一個連通塊的連上
    tot[++kk]=block;//該摧毀的都摧毀了
    for(int i=k;i>=1;i--){//加起來(倒著做方便處理)
          block++;//多了一個星球多了一個聯通塊
          vis[fire[i]]=0;//加上了
          for(int j=h[fire[i]];j;j=bot[j].x)//相連的沒摧毀的點都連線
            if(!vis[bot[j].y]&&find(fire[i])!=find(bot[j].y)){
                block--;//連上就減一個聯通塊
                f[find(fire[i])]=find(bot[j].y);
          }
        tot[++kk]=block;//記錄
      }
    for(int i=kk;i>=1;i--) printf("%d\n",tot[i]);//倒著輸出
    return 0;
}

洛谷P2502 [HAOI2006]旅行

題目分析:

這道題邊數只有5000,所以我們可以每次做一邊最小生成樹後,更新一下答案,然後再刪除一條邊,在繼續做下去。

#include <cstdio>
#include <algorithm>
#define maxn 600
#define maxm 5010
using namespace std;
int n,m,s,t;
int father[maxn];
int ans1,ans2;
struct rec{int a,b,len;} c[maxm];
inline int read(){
    int x=0,w=1;char ch;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
    return x*w;
}
inline int cmp(rec a,rec b){return (a.len<b.len);}
int getfather(int x){ return x==father[x]?x:father[x]=getfather(father[x]);}
int gcd(int x,int y){/*if(y!=0) return gcd(y,x%y);return x;*/return y==0?x:gcd(y,x%y);}
int main(){
    n=read(),m=read();
    for (int i=1;i<=m;i++) c[i].a=read(),c[i].b=read(),c[i].len=read();
    s=read(),t=read();
    sort(c+1,c+1+m,cmp);//先排序一次
    for (int i=1;i<=m;i++){//做m次最小生成樹
        int j;
        for (j=1;j<=n;j++) father[j]=j;
        for (j=i;j<=m;j++){
            int fa,fb;
            fa=getfather(c[j].a); fb=getfather(c[j].b);
            if (fa==fb) continue;
            father[fa]=fb;
            if (getfather(s)==getfather(t)) break;
            //當此時s和t在同一個連通塊中便退出迴圈
        }
        if ((i==1)&&(getfather(s)!=getfather(t))) {printf("IMPOSSIBLE\n");return 0;}
        //如果在還沒有刪除邊的情況下,s和t還是不能連通,說明s和t之間沒有路徑可以相互到達
        if (getfather(s)!=getfather(t)) break;  
        //這個判斷是當某條邊刪除後不能s到t不能連通時,邊退出迴圈
        if (ans1*c[i].len>=ans2*c[j].len) ans1=c[j].len,ans2=c[i].len;
        //更新一下答案
    }
    int x=gcd(ans1,ans2);//因為是要最簡分數,求一下最大公約數
    if (x==ans2) printf("%d\n",ans1/ans2);
    else printf("%d/%d\n",ans1/x,ans2/x);
    return 0;
}