歐的最小生成樹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的這條邊的起始點和終點。
- 排序
排序這個操作,是為了實現最小生成樹的一種方式。它並不死板。我們排出來的順序,是在為後續的選邊做準備。
這樣想:既然我們要的是“最小”。那麼我們從"最小"->"最大"選,也就一定比從"最大"->"最小"選更快更好。
既然如此,我們在求最小生成樹時從小到大排序,也就是先選小的,要更好。
那麼,在求最大生成樹時,從大到小排序,也就是先選大的,是不是更好呢?
因此,我們說,排序這個操作並不死板,也並不是固定的。你可以根據題目要求和題中所給條件,選擇更加合適的排序方法(從大到小?從小到大?還是先前者後後者?還是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就代表不可以啦。
簡單題:
接下來都是簡單題,沒什麼好說的,重要的地方主要有兩點。
一點是在有的細節方面要注意,另一點就是細微卻又巧妙的變化。
沒什麼好說的,就是板子。純板子。
這裡我沒有寫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; }
這個有一點變化,由求最小總值改為了求最小總值中的最大值,只需要改一點。
先放程式碼:改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; }
改++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; }
改退出條件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:新增一道題。