PKU1679(The Unique MST)判断最小生成树的唯一性-次小生成树

/*
 *题目大意:
 *给出一个连通无向图,判断它的最小生成树是否唯一;
 *如果唯一,输出生成树的大小,否则输出"Not Unique!";
 *
 *算法思想:
 *本题可以尝试求与最小生成树权值相等的树是否存在;
 *但是更好的思路是直接求次小生成树,如果次小生成树等于最小生成树;
 *则说明最小生成树不唯一,否则最小生成树一定是唯一的;
 *
 *次小生成树的求法详见http://blog.csdn.net/jarily/article/details/8883858;
 *
 *本题分别写了Prim算法的和Kruskal算法版本的,不过Kruskal的超时了,弱渣不知道如何优化了,求高手护之;
**/
#include<iostream>
#include<string>
#include<cstdio>
#include<map>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;

const int INF=0xfffff;
const int N=101;


int G[N][N],dist[N];
int path[N][N];//从i到j的路径上最大边的权值
int pre[N],visit[N];
bool used[N][N];//边是否在该MST中
int n,m;

int Prim()
{
    int Mst=0;
    memset(visit, 0, sizeof(visit));
    memset(used, 0, sizeof(used));
    memset(path, 0, sizeof(path));
    visit[1]=1;
    for(int i=1; i<=n; ++i)
    {
        dist[i]=G[1][i];
        pre[i]=1;
    }
    for(int i=1; i<n; ++i)
    {
        int u=-1;
        for(int j=1; j<=n; ++j)
        {
            if(!visit[j])
            {
                if(u==-1||dist[j]<dist[u])
                    u=j;
            }
        }
        used[u][pre[u]]=used[pre[u]][u]=true;//加入MST
        Mst+=G[pre[u]][u];
        visit[u]=1;
        for(int j=1; j<=n; ++j)
        {
            if(visit[j]&&j!=u)//求从u到j的路径上最大边的权值
            {
                path[u][j]=path[j][u]=max(path[j][pre[u]],dist[u]);
            }
            if(!visit[j])
            {
                if(dist[j]>G[u][j])//更新相邻顶点的dist
                {
                    dist[j]=G[u][j];
                    pre[j]=u;
                }
            }

        }
    }
    return Mst;
}

int main()
{
    //freopen("C:\\Users\\Administrator\\Desktop\\kd.txt","r",stdin);
    int tcase;
    scanf("%d",&tcase);
    while(tcase--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0; i<=n; i++)
            for(int j=i+1; j<=n; j++)
                G[i][j]=G[j][i]=INF;
        int u,v,w;
        for(int i=1; i<=m; ++i)
        {
            scanf("%d%d%d",&u,&v,&w);
            G[u][v]=G[v][u]=w;
        }
        int Mst=Prim();
        int res=INF;
        for(int i=1; i<=n; ++i)
        {
            for(int j=1; j<=n; ++j)
                if(i!=j)
                {
                    if(!used[i][j])
                        res=min(res,Mst+G[i][j]-path[i][j]);
                }
        }
        if(res==Mst)
            puts("Not Unique!");
        else
            printf("%d\n",Mst);
    }
    return 0;
}




下面为超时的Kruskal版本


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

const int INF=0xffffff;
const int N=101;

struct Edge
{
    int u,v,w;
    bool select;
} edge[N];

bool cmp(Edge a,Edge b)
{
   // if(a.w==b.w)
     //   return a.u<b.u;
    return a.w<b.w;
    /*if(a.w!=b.w)
        return a.w<b.w;
    if(a.u!=b.u)
        return a.v<b.v;*/
}

struct node
{
    int to;
    int next;
};

node link[N];//边数组
int len;//边数组中数据的个数
int head[N];//头结点
int end[N];//尾结点
int length[N][N];//每两点在MST上路径的最长边长
int f[N];
int n,m;

int Find(int x)
{
    if(x!=f[x])
        f[x]=Find(f[x]);
    return f[x];
}

void Merge(int x,int y)
{
    f[y]=x;
}

void Kruskal()
{
    for(len=0; len<n; len++) //初始化邻接表,对于每个结点添加一条指向其自身的边,表示以i为代表元的集合只有点i
    {
        link[len].to=len+1;
        link[len].next=head[len+1];
        end[len+1]=len;
        head[len+1]=len;
    }
    sort(edge+1,edge+1+m,cmp);

    int vertex=0;
    for(int i=1; i<=m; i++)
    {
        if(vertex==n-1)
            break;
        if(edge[i].w<0)
            continue;
        int x=Find(edge[i].u);
        int y=Find(edge[i].v);
        if(x!=y)//修改部分,遍历两个结点所在的集合
        {
            //每次合并两个等价类的时候,分别属于两个等价类的两个点间的最长边一定是当前加入的边
            for(int j=head[x]; j!=-1; j=link[j].next)
            {
                for(int k=head[y]; k!=-1; k=link[k].next)
                {
                    length[link[j].to][link[k].to]=length[link[k].to][link[j].to]=edge[i].w;
                }
            }
            link[end[y]].next=head[x];//合并两个邻接表
            end[y]=end[x];
            Merge(x,y);
            vertex++;
            edge[i].select=true;
        }
    }
}

int main()
{
    //freopen("C:\\Users\\Administrator\\Desktop\\kd.txt","r",stdin);
    int tcase;
    scanf("%d",&tcase);
    while(tcase--)
    {
        scanf("%d%d",&n,&m);
        memset(length,0,sizeof(length));
        memset(head,-1,sizeof(head));
        memset(end,-1,sizeof(end));
        int u,v,w;
        for(int i=1; i<=m; ++i)
        {
            scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
        }
        for(int i=1; i<=n; i++)
            f[i]=i;
        Kruskal();
        int Mst=0;
        for(int i=1; i<=m; i++)
        {
            if(edge[i].select)
                Mst+=edge[i].w;
        }
        int res=INF;
        for(int i=1; i<=m; i++)
        {
            if(!edge[i].select)
                res=min(res,Mst+edge[i].w-length[edge[i].u][edge[i].v]);
        }
        if(res==Mst)
            puts("Not Unique!");
        else
            printf("%d\n",Mst);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值