1. 程式人生 > 實用技巧 >歐的最小生成樹QWQ(8.15更新)

歐的最小生成樹QWQ(8.15更新)

1.Kruskal

Kruskal主要思想:加邊法。
K從本質上說,其實是一個貪心演算法。轉換過來就是:既然要求最小的生成樹,那麼我們加就加最小的邊。
根據這個想法,K的演算法是將所有的邊從小到大排序,從最小的邊加起,一直加到滿足生成樹的條件,也就是有n-1條邊時,這個演算法就算是結束啦。

但是,注意:其實並不是所有的“最小”的邊都可以加上。也就是說,我們並不能把邊從小到大排完序,直接選擇最小的n-1條加進去,算作我們的最小生成樹。
因為可能在這n-1條最小邊裡,加屬於其中的一條,不如加不屬於其中的另一條邊更優。
這裡的更優有很多種情況,比如兩個點之間有多條邊,而其中的兩條就屬於這n-1條最小邊,那麼加入它們,就會導致有的點無法連上;

又或者像下面這個例子:
有ABCD四個點,AB之間邊權為1,BC之間邊權為3,AC之間邊權為2,AD之間邊權為4,CD之間邊權為5。那麼在這種情況下,如果像上面所說的直接加n-1條最小邊,就是加邊權為1,2,3的這三條邊,就會導致D沒有與ABC相連,從而不符合最小生成樹。那麼我們就不能選這三條,而是選擇“更優”的1,2,4。

到這裡,就有一個新的問題:怎麼樣才能知道加的是“更優”的邊呢?

這就需要我們引入新的手段:並查集

這樣,Kruskal的兩個中心就出來了。

0.前置

為了防止不知道後面的符號都是代表什麼,我先把結構體寫上。

struct E{int from,to,w;}e[maxn];

from和to代表著邊權為w的這條邊的起始點和終點。

  1. 排序
    排序這個操作,是為了實現最小生成樹的一種方式。它並不死板。我們排出來的順序,是在為後續的選邊做準備。
    這樣想:既然我們要的是“最小”。那麼我們從"最小"->"最大"選,也就一定比從"最大"->"最小"選更快更好。
    既然如此,我們在求最小生成樹時從小到大排序,也就是先選小的,要更好。
    那麼,在求最大生成樹時,從大到小排序,也就是先選大的,是不是更好呢?
    因此,我們說,排序這個操作並不死板,也並不是固定的。你可以根據題目要求和題中所給條件,選擇更加合適的排序方法(從大到小?從小到大?還是先前者後後者?還是0和1進行排序?),這一切,都是由你自己決定。

bool Cmp(E a,E b){return a.w<b.w;}

上面這個方式是我自己常用的,用結構體E儲存,w為邊權。這個是從小到大的排序方式。
其實也可以直接在結構體內過載運算子,但我實在是不太愛用(總感覺會寫錯),以後會補上過載的寫法。

整個排序的操作其實也就這些,雖然是短短一行程式碼,但還是說:這個是很靈活的
一定要根據需要。選擇適合的排序方式。不要直接無腦從小到大排。很多板子題是要求的最小生成樹,但是也有變了一點的“最大生成樹”。
切記:一定要根據需求來。

這裡多說一點廢話:(寫到這裡想到的,就順手寫了,免得往後忘)其實最小生成樹題目的模板還是很容易看出來的,但是,很多時候是:
寫著寫著板子,突然發現模板並不能實現題目中的要求。

到這裡,就有必要說一點:其實那些“寫不出來”的模板題,往往只是變了一個點。而往往這個點,就是整個演算法的核心。那麼想要寫出來,就要對這個演算法有一定的瞭解。
我個人認為:這個時候寫不出來,就應該去直接看題解。當然,自己想也可以。只不過你的寫不出來,並不是你不會,甚至並不是你掌握不了這個演算法。往往只是你沒有見過而已。
那麼在後面,我會給出那些巧妙的解決方法和技巧。這個東西在有一定的理解上是很容易接受的。

2.並查集
並查集這個操作的作用和必要性,在前面已經解釋過了。在這裡就再全面說一下。
前面說並查集的存在是為了讓我們找到更優的邊。到了這,我們就把更優這個概念再細化一下。
所謂的更優,其實前面也說過了:大部分情況下是為了避免加了重複的邊而使得有的點沒有連上。那麼為了避免這個不應該出現的錯誤,我們就需要有東西來幫助我們記錄:
哪些加過,哪些沒有加過,從而確定加哪些

對,你沒有看錯,是加哪些
你可能會問:最小生成樹加的不是邊嗎,為什麼要記錄加點呢。

其實吧,這個也沒有固定的演算法。我也無數次說過了:這個是很靈活的。

因此,我認為:選擇記錄加邊和加點應該都是可以的,但是目前我還沒有見過記錄邊的。可能是我做的題不多。不過我相信還是有的。
如果日後我見到了,我會把方法加上的。

那麼記錄點的目的,其實也是為了我們加“更優”的邊做的準備。
在這裡,我簡單說一下加點的過程:

  • 並查集的作用想必學到這裡的應該知道個差不多。不知道也沒什麼,如果我能把你說明白,就聽著就好。如果說不明白,建議你還是去看專門的講解。
  • 我們選邊,是從小到大,按照排好的序選的。因此我們首先選的,一定是最小的邊。
  • 接下來,我們先看看它們的祖先。如果它們的祖先不同,就說明它們之間的邊是可以加的。
  • 要問為什麼,這裡就應該說說使用並查集的規則了。
    如果我們加了一條邊,那麼就統一這條邊的兩點的祖先,代表著它們已經加入”最小生成樹“這個集合中。
    如果當我們要加一條邊,發現它們的祖先相同,就說明它們已經是都屬於最小生成樹這個集合了,那麼它們之間的邊也就沒有加的必要了。

到這裡你可能會說,萬一它們之間的邊小呢?
告訴你:不可能。
既然我們選邊的順序是按照排序的順序來的,那麼當有兩個點在這條邊被選擇之前就已經加進去了,那麼說明:這兩個點一定有別的和它們相連的邊是比這條邊小的。

對於這種加法,我們可以按以下幾種方式理解。

  • 第一種我們按照正常的方式理解。將沒有加入生成樹的點集合設為U,已經加入的設為V。那麼並查集的操作就是:將V中的點祖先統一,並且此時,U中的任意一點的祖先都與V中的不同。這樣來做,我們就能夠通過是否祖先相同,來確定點是U中的還是V中的,是可以加的還是已經加了的。
  • 第二種方式我們可以看作是連通塊。如果不知道連通塊是什麼,可以把它單純的理解為一個連通塊中的所有點一定可以到達彼此。那麼轉換到並查集的角度來說,我們就可以認為,祖先相同的點屬於同一個連通塊。也就是說,一旦兩個點的祖先相同,那麼它們之間一定有一條路徑,使得它們之間互通。
  • 第三種我們就換到K演算法的角度去想。其實這一種是建立在對K演算法的一定的瞭解之上的,也相當於一種結論:
    祖先相同,則不可以加此條邊。祖先不同,就可以加。
    加一條邊時,看祖先是否相同。如果相同,則跳過;不相同則加入,並統一祖先。

到了這裡,其實在第三種中,我已經把程式碼的順序和思路說出來了。真正的程式碼實現和第三種的思路是很像的。

當然,你也可以當作是我按照程式碼說的第三種。

for(int i=1;i<=n;i++)fa[i]=i;
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}

上面的是並查集的初始化和查詢的基礎操作。當然也是我個人常用的。這個總的來說,還是比較固定的。目前我沒有見過在這個地方有改動的。

int u=Find(e[i].from);
int v=Find(e[i].to);

/×這個地方建議這麼分成兩行寫,因為如果寫在一起,很有可能會寫成int u=e[i].from,v=e[i].to;而把Find()函式忘記。×/
if(v==u)continue;
fa[u]=v;//fa[v]=u都可以,選一個

上面就是在void K()也就是K演算法中的並查集操作部分。就我個人來說,比較喜歡將初始化和排序一併寫在演算法主要部分中。 當然,這個也是很靈活的。如果你用一次就要初始化一次,那麼把初始化放在裡面比較好。但如果直接用兩次及以上,那還是放在外面吧。

3.全部程式碼

先說輸入這部分

int x,y,z;scanf("%d%d%d",&x,&y,&z);
e[i].from=x;e[i].to=y;e[i].w=z;

這個是使用K演算法時邊和點的儲存方式,當然,有向圖沒什麼疑問,但是無向圖既可以只像上面那麼寫,也可以像下面這麼寫。

e[i].from=y;e[i].to=x;e[i].w=z;

無向圖甚至可以兩種方式同用,就是既正向建邊,也反向建邊。

至於為什麼,其實在上面的並查集部分就已經說過了。因為在統一了祖先之後,不管後面遇到正著的from:x,to:y;還是反著的from:y,to:x;其結果都是一樣的。
就是隻要加過它們了,就沒必要去擔心後面會不會加重。
可以這樣想。我們把已經統一祖先的點塗黑,而一個白點只會被塗黑一次。那麼前面塗黑過了,後面再遇到怎麼樣的它們,也只能忽略,而不會再加一遍。

那麼這樣就可以解決一個問題:加無向圖的邊時,用不用建兩次邊;或者是在有的題中,用不用再開一個vis陣列去記錄哪些邊建過了,哪些邊沒建過。

不用!!!

不過悄悄說一句,在有的題中,開一個vis去記錄哪些邊加過,而避免做無用功,可以防止T掉~
我會在後面給出這道題。

然後是退出條件。
畢竟這個演算法我們不能讓它一直跑完m條邊。所以我們讓它的退出條件為

if(++cnt==n-1)return;

這裡的話變化著實有點多。有很多奇妙的不同型別的題,都是通過巧妙的改動這個地方實現的。真的是很靈活。在這裡就先簡單說說,後面有專門的題。
當退出條件改為了:

if(++cnt==n-k)return;

就會由建一棵最小生成樹,改為建K棵。
由上面兩種可以得出:當要建幾條邊時,就讓退出條件變成cnt等於幾。
那麼也可以說成是cnt就是我們要建的邊的條數。
當退出條件改為:

if(Find(s)==Find(t))return;

就會變成當s和t第一次連通時,也就是s點和t點之間的直接路徑,其實也可以說是最短吧。

接下來就沒什麼注意事項了。
全部程式碼:

模板題:

純板子,適應K和P兩種演算法。在做下面的較難題之前,建議先把板子打熟。

對應模板題最小生成樹模板

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2e5+1;
const int mod=31011;
int n,m,fa[maxn],ans=0,cnt=0;
struct E{
    int from,to,w;
}e[maxn];
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
bool Cmp(E a,E b){return a.w<b.w;}
void K(){
    for(int i=1;i<=n;i++)fa[i]=i;
    sort(e+1,e+m+1,Cmp);
    for(int i=1;i<=m;i++){
        int u=Find(e[i].from),v=Find(e[i].to);
        if(u==v)continue;
        fa[v]=u;
        ans+=e[i].w;
        if(++cnt==n-1)return;
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        e[i].from=x;e[i].to=y;e[i].w=z;
    }
    K();
    printf("%d\n",ans);
    return 0;
}

如果題目要求輸出-1代表不行的話,可以把K的返回型別改為int,然後在判斷if(++cnt==n-1)return ans;後,迴圈外加個return -1;
因為如果正常情況下,會加滿n-1條邊,然後返回ans,而如果加不滿的話,就會退出迴圈,那時返回-1就代表不可以啦。

簡單題:

接下來都是簡單題,沒什麼好說的,重要的地方主要有兩點。

一點是在有的細節方面要注意,另一點就是細微卻又巧妙的變化。

純純的板子題QWQ

沒什麼好說的,就是板子。純板子。

這裡我沒有寫K的演算法,寫的是Prim的,僅供參考:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e4+1;
const int INF=0x3f3f3f3f;
int head[maxn],vis[maxn],d[maxn],len=0,tot=0,n;
struct E{int to,next,w;}e[maxn];
void Adde(int x,int y,int z){e[++len].to=y;e[len].w=z;e[len].next=head[x];head[x]=len;}
int Prim(){
    int pos=1,ans=0;
    for(int i=2;i<=n;i++)d[i]=INF;
    for(int i=head[1];i;i=e[i].next)d[e[i].to]=min(d[e[i].to],e[i].w);
    while(++tot<n){
        int Min=INF;
        vis[pos]=1;
        for(int i=1;i<=n;i++)if(!vis[i]&&d[i]<Min)Min=d[i],pos=i;
        ans+=Min;
        for(int i=head[pos];i;i=e[i].next){
            int v=e[i].to;
            if(d[v]>e[i].w&&!vis[v])d[v]=e[i].w;
        }
    }
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            int dis;scanf("%d",&dis);
            Adde(i,j,dis);
        }
    }
    printf("%d\n",Prim());
    return 0;
}

求最小中的最大值QWQ
求最小中的最大值QAQ

這個有一點變化,由求最小總值改為了求最小總值中的最大值,只需要改一點。

改ans+=e[i].w一下成ans=max(ans,e[i].w);

先放程式碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e4+1;
const int INF=0x3f3f3f3f;
int head[maxn],vis[maxn],len=0,tot=0,n,m,d[maxn];
struct E{int to,w,next;}e[maxn<<1];
void Adde(int x,int y,int z){e[++len].to=y;e[len].w=z;e[len].next=head[x];head[x]=len;}
int Prim(){
    int ans=0,pos=1;
    for(int i=2;i<=n;i++)d[i]=INF;
    for(int i=head[1];i;i=e[i].next)d[e[i].to]=min(d[e[i].to],e[i].w);
    while(++tot<n){
        int Min=INF;vis[pos]=1;
        for(int i=1;i<=n;i++)if(!vis[i]&&Min>d[i])Min=d[i],pos=i; 
        ans=max(ans,Min);
        for(int i=head[pos];i;i=e[i].next){
            int v=e[i].to;
            if(!vis[v]&&d[v]>e[i].w)d[v]=e[i].w;
        }
    }
    return ans;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        Adde(x,y,z);Adde(y,x,z);
    }
    printf("%d\n",Prim());
    return 0;
}
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e5+1;
int fa[maxn],ans,cnt=0,n,m;
struct E{int from,to,w;}e[maxn];
bool Cmp(E a,E b){return a.w<b.w;}
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
int K(){
    for(int i=1;i<=n;i++)fa[i]=i;
    sort(e+1,e+m+1,Cmp);
    for(int i=1;i<=m;i++){
        int u=Find(e[i].from);
        int v=Find(e[i].to);
        if(u==v)continue;
        fa[u]=v;
        ans=max(ans,e[i].w);
        if(++cnt==n-1)return ans;
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        e[i].from=x;e[i].to=y;e[i].w=z;
    }
    printf("%d %d\n",n-1,K());
    return 0;
}

加限制

e[i].w>=c;

包括這道和下面一道,都需要求座標間的距離。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2001;
int fa[maxn],cnt=0,len=0,x[maxn],y[maxn],n,c;
struct E{int from,to,w;}e[19260817];
bool Cmp(E a,E b){return a.w<b.w;}
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
int K(){
    int ans=0;
    for(int i=1;i<=n;i++)fa[i]=i;
    sort(e+1,e+len+1,Cmp);
    for(int i=1;i<=len;i++){
        int u=Find(e[i].from);
        int v=Find(e[i].to);
        if(u==v)continue;
        fa[u]=v;
        ans+=e[i].w;
        if(++cnt==n-1)return ans;
    }
    return -1;
}
int main(){
    scanf("%d%d",&n,&c);
    for(int i=1;i<=n;i++)scanf("%d%d",&x[i],&y[i]);
    for(int i=1;i<=n;i++){
        for(int j=1;j<i;j++){
            int dis=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
            if(dis>=c){e[++len].from=i;e[len].to=j;e[len].w=dis;}
        }
    }
    printf("%d\n",K());
    return 0;
}

反向變化

注意double的處理

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
double ans=0;
int len=0,g[maxn][maxn],n,m,x[maxn],y[maxn],fa[maxn],cnt=0;
struct E{
    int from,to;
    double w;
}e[10010000];
bool Cmp(E a,E b){return a.w<b.w;}
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
void K(){
    for(int i=1;i<=len;i++){
        int u=Find(e[i].from),v=Find(e[i].to);
        if(u==v)continue;
        fa[u]=v;
        ans+=e[i].w;
        if(++cnt==n-1)return;
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&x[i],&y[i]);
    }
    for(int i=1;i<=m;i++){
        int a,b;scanf("%d%d",&a,&b);
        e[i].from=a,e[i].to=b;e[i].w=0;
    
    }
    len=m+1;
    for(int i=1;i<n;i++){
        for(int j=i+1;j<=n;j++){
    
                e[len].from=i,e[len].to=j;
                double dis=(double)(sqrt((double)(x[i]-x[j])*(x[i]-x[j])+(double)(y[i]-y[j])*(y[i]-y[j])));
                e[len++].w=dis;
    
            
        }
    }
    for(int i=1;i<=n;i++)fa[i]=i;
    sort(e+1,e+len+1,Cmp);
    K();
    printf("%.2f\n",ans);
    return 0;
}

k棵最小生成樹

改++cnt==n-1為++cnt==n-k;

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e4+1;
int n,m,k,fa[maxn],ans=0,cnt=0;
struct E{int from,to,w;}e[maxn];
bool Cmp(E a,E b){return a.w<b.w;}
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
bool K(){
    sort(e+1,e+m+1,Cmp);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m;i++){
        int u=Find(e[i].from);
        int v=Find(e[i].to);
        if(u==v)continue;
        fa[u]=v;
        ans+=e[i].w;
        if(++cnt==n-k)return 1;
    }
    return 0;
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=m;i++){
        int x,y,l;scanf("%d%d%d",&x,&y,&l);
        e[i].from=x;e[i].to=y;e[i].w=l;
    }
    if(K())
    printf("%d\n",ans);
    else printf("No Answer\n");
    return 0;
}

s到t的最小生成樹

改退出條件if(++cnt==n-1)break為if(Find(s)==Find(t))break;

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2e4+4;
int fa[maxn],ans=0,cnt=0,n,m,s,t,len=0;
struct E{int from,to,w;}e[maxn];
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
bool Cmp(E a,E b){return a.w<b.w;}
int K(){
    sort(e+1,e+m+1,Cmp);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m;i++){
        int u=Find(e[i].from);
        int v=Find(e[i].to);
        if(u==v)continue;
        fa[u]=v;
        if(Find(s)==Find(t))return e[i].w;
        //if(++cnt==n-1)return;
    }
}
int main(){
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=m;i++){
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        e[i].from=x;e[i].to=y;e[i].w=z;
    }
    printf("%d\n",K());
    return 0;
}

被除去最大,留下最小

被留下最小,除去最大

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e4+1;
int n,m,head[maxn],len=0,tot=0,d[maxn],vis[maxn],sum=0;
const int INF=0x3f3f3f3f;
struct E{int to,w,next;}e[maxn];
void Adde(int x,int y,int z){e[++len].to=y;e[len].w=z;e[len].next=head[x];head[x]=len;}
int Prim(){
    int ans=0,pos=1;
    for(int i=2;i<=n;i++)d[i]=INF;
    for(int i=head[1];i;i=e[i].next)d[e[i].to]=min(d[e[i].to],e[i].w);
    while(++tot<n){
        int Min=INF;vis[pos]=1;
        for(int i=1;i<=n;i++)if(!vis[i]&&d[i]<Min)Min=d[i],pos=i;
        ans+=Min;
        for(int i=head[pos];i;i=e[i].next){
            int v=e[i].to;
            if(!vis[v]&&d[v]>e[i].w)d[v]=e[i].w;
        }
    }
    return sum-ans;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        if(z!=0)sum+=z,Adde(x,y,z),Adde(y,x,z);
    }
    printf("%d\n",Prim());
    return 0;
}
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e5+1;
int fa[maxn],ans=0,cnt=0,n,m,k;
struct E{int from,to,w;}e[maxn];
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
bool Cmp(E a,E b){return a.w>b.w;}
void K(){
    sort(e+1,e+m+1,Cmp);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m;i++){
        int u=Find(e[i].from);
        int v=Find(e[i].to);
        if(u==v)continue;
        fa[u]=v;
        ans+=e[i].w;
        if(++cnt==k)return;
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=m;i++){
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        e[i].from=x;e[i].to=y;e[i].w=z;
    }
    K();
    printf("%d\n",ans);
    return 0;
}

轉點權為邊權

將每個點的點權設定為通向超級匯點的邊權,從而正常跑最短路。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e5+1;
int n,len=0,fa[maxn],cnt=0,ans=0,head[maxn];
struct E{int to,from,next,w;}e[maxn<<1];
void A(int x,int y,int z){e[++len].to=y;e[len].w=z;e[len].from=x;e[len].next=head[x];head[x]=len;}
bool Cmp(E a,E b){
    return a.w<b.w;
}int Find(int x){return fa[x]==x?x:Find(fa[x]);}
int K(){
    sort(e+1,e+len+1,Cmp);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=len;i++){
        int u=Find(e[i].from);
        int v=Find(e[i].to);
        if(u==v)continue;
        fa[u]=v;
        ans+=e[i].w;
        if(++cnt==n)return ans;
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int w;scanf("%d",&w);
        A(i,n+1,w);A(n+1,i,w);
    }for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            int w;scanf("%d",&w);
            if(i!=j)A(i,j,w),A(j,i,w);
        }
    }printf("%d\n",K());
    return 0;
}

較難題:
接下來的題目難度較大,已經有了變化。想要找出其中的模板並不向上面那樣容易。 注:下面題目都需要求座標間距離。
還可以理解的變化 這道題主要在於理解題意,並且轉化成最小生成樹相應的模板題。總的來說,這道題並不難理解和轉化。 先放程式碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1e4+5;
int fa[maxn],n,s,p,len=0,x[maxn],y[maxn],cnt=0,g[maxn][maxn];
double ans=0;
struct E{
    int from,to;
    double w;
}e[250001];
bool Cmp(E a,E b){return a.w<b.w;}
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
void K(){
    for(int i=1;i<=p;i++)fa[i]=i;
    sort(e+1,e+len+1,Cmp);
    for(int i=1;i<=len;i++){
        int u=Find(e[i].from);
        int v=Find(e[i].to);
        if(u==v)continue;
        ans=e[i].w;
        fa[u]=v;
        if(++cnt==p-s)return;
    }
}
int main(){
    scanf("%d%d",&s,&p);
    for(int i=1;i<=p;i++){
        scanf("%d%d",&x[i],&y[i]);     
    }
    for(int i=1;i<=p;i++){
        for(int j=1;j<=p;j++){
           // if(!g[i][j]){
                double dis=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
                e[++len].from=i;e[len].to=j;e[len].w=dis;
                //g[i][j]=g[j][i]=1;
            //}
        }
    }
    K();
    printf("%.2f\n",ans);
    return 0;
}

可以看到,加不加記錄陣列g都沒有關係。

這道題的主要思想就在於++cnt==p-s

較可以理解的變化 先放程式碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1e4+1;
const double INF=0x3f3f3f3f;
int n,x[maxn],y[maxn];
bool vis[maxn];
double ans,d[maxn];
double C(int a,int b){
    return sqrt((double)(x[a]-x[b])*(x[a]-x[b])+(double)(y[a]-y[b])*(y[a]-y[b]));
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&x[i],&y[i]);
        d[i]=INF; 
    }
    int pos;d[1]=0;
    for(int i=1;i<=n;i++){
        double Min=INF;
        for(int j=1;j<=n;j++)if(!vis[j]&&d[j]<Min)Min=d[j],pos=j;
        ans+=Min;vis[pos]=1;
        for(int j=1;j<=n;j++){
            double dis=C(pos,j);
            if(dis<d[j])d[j]=dis;
        }
    }
    printf("%.2f\n",ans);
    return 0;
}
較難理解的變化: 先放程式碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1e4+5;
int fa[maxn],len=0,n,k,cnt=0,CNT=0,x[maxn],y[maxn];
double ans[maxn];
struct E{int from,to;double w;}e[10000001];
bool Cmp(E a,E b){return a.w<b.w;}
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
void K(){
    
    sort(e+1,e+len+1,Cmp);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=len;i++){
        int u=Find(e[i].from);
        int v=Find(e[i].to);
        if(u==v)continue;
        fa[u]=v;
        ans[++CNT]=e[i].w;
        if(++cnt==n-1)return;
    }
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&x[i],&y[i]);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){ 
                e[++len].from=i;e[len].to=j;
                e[len].w=(double)sqrt((double)(x[i]-x[j])*(x[i]-x[j])+(double)(y[i]-y[j])*(y[i]-y[j]));  
         }
    }
    K();
    printf("%.2f\n",ans[n-k+1]);
    return 0;
}//正常建邊,相當於雙向建邊,不可單向建,然後正常跑。答案就是在n個點中建n-1條邊,邊值最小的,然後數n+1-k,在n-1條邊中刨去最小的幾條,因為它們是一個部落的,-k,在剩下的邊中找最小的即所求

再加兩道比較像的很好的題,都是屬於有兩種限制,並且同時要求兩種限制和最小 好題QWQ 好題QAQ 還有一道題,有別的做法,不太好往最小生成樹上想 (continue.....)QWQ後面會繼續寫的。。。題還沒有整理完,明天再排排版,整理一下題目;後續會把Prim也加上的QWQ~
Date 7.25 : 大致結構。 7.28 : 新增兩道題。 8.2 : 整理題目,並新增一道題。 8.15:新增一道題。