最小生成树例题及其总结

目录:
洛谷 P2820 局域网
POJ 2421 Constructing Roads
洛谷 P1111 修复公路
洛谷 P2700 逐个击破
洛谷 P1197 [JSOI2008]星球大战
洛谷 P2502 [HAOI2006]旅行

关于最小生成树的问题差不多有如下几个:

1.模板(加一点修改,比如给的所有边的总边权-最小生成树的总边权或者有些边已经建好,求其他的)如第一题,第三题。第四题也差不多,只是判断了特殊点在不在同一棵树中
2.虚点(可以建立一个虚点,然后将其他点与他相连)
3.将给的边先存下来,然后再反过来做(称为时光倒流)
4.判断是不是联通的,或者输出最大边权。如第三题

洛谷 P2820 局域网

题目分析:

这道题直接把所有边权的总和记录下来,然后做一遍最小生成树,最后总边权和减去最小生成树中的边权和就是答案。

#include
#include
#include
#include
#include
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.wint 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;
}

POJ 2421 Constructing Roads

题目描述:

注意这题为多组输入输出!!!(一开始自己没有注意到,就老是wa,后面上网搜了下之后才晓得的)
题目意思:有一些边已经建好了,要你在此基础上再去求一遍最小生成树
主要是为了练一下prim,没什么好讲的,直接上代码了。

#include
#include
#include
#include
#include
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]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;
}

洛谷 P1111 修复公路

题目分析:

我们可以直接做最小生成树,然后输出最大的那条边的权值是多少即可。题目中还要求判断一下是否联通,然后本蒟就想了一个办法,把fa数组扫一遍,如果有不同的父亲,那么就输出-1,表示所有的边都连完后还是不连通。但是只得了90分。后面在网上找了一下题解,有一篇是这么做的。(程序中备注了)

下面是90分的程序

#include
#include
#include
#include
#include
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.winline 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
#include
#include
#include
#include
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.winline 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(num1) printf("-1");//这就是网上大佬的判断,很迷,为什么我的不能过
    else printf("%lld",ans);
    return 0; 
}

洛谷 P2700 逐个击破

题目分析:

这道题也差不多是最小生成树裸题,只是在合并的时候判断一下,最后用原本的总边权减去生成树中的边权即可

#include
#include
#include
#include
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;iint 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;iint 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);
}

洛谷 P1197 [JSOI2008]星球大战

这道题比上面那道题稍微复杂一些(用到了时光倒流的思想),不过也差不多,直接上代码吧。

#include
#include
#include
#include
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;iint 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 
#include 
#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.lenint 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;
}

你可能感兴趣的:(最小生成树,模板,总结,数据结构)