最小生成樹例題及其總結
阿新 • • 發佈:2019-01-06
關於最小生成樹的問題差不多有如下幾個:
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;
}