ac之次小生成树

修路方案

时间限制: 3000 ms  |  内存限制: 65535 KB
难度: 5
描述

南将军率领着许多部队,它们分别驻扎在N个不同的城市里,这些城市分别编号1~N,由于交通不太便利,南将军准备修路。

现在已经知道哪些城市之间可以修路,如果修路,花费是多少。

现在,军师小工已经找到了一种修路的方案,能够使各个城市都联通起来,而且花费最少。

但是,南将军说,这个修路方案所拼成的图案很不吉利,想让小工计算一下是否存在另外一种方案花费和刚才的方案一样,现在你来帮小工写一个程序算一下吧。

输入
第一行输入一个整数T(1<T<20),表示测试数据的组数
每组测试数据的第一行是两个整数V,E,(3<V<500,10<E<200000)分别表示城市的个数和城市之间路的条数。数据保证所有的城市都有路相连。
随后的E行,每行有三个数字A B L,表示A号城市与B号城市之间修路花费为L。
输出
对于每组测试数据输出Yes或No(如果存在两种以上的最小花费方案则输出Yes,如果最小花费的方案只有一种,则输出No)
样例输入
2
3 3
1 2 1
2 3 2
3 1 3
4 4
1 2 2
2 3 2
3 4 2
4 1 2
样例输出
No

Yes


题目分析求次小生成树一般有两种办法:

a.利用克鲁斯卡尔先生成一次最小生成树并保存每条树上的边的下标,然后遍历这些树上的边,然后在把每次树上的边执行删除-->重新建树-->更新最小权值-->恢复的过程,每次删除树一条边后,就破坏了原来的树,重新建成的树一定不是原来的树,重新建成的树的最小权值就是次小的,注意每次删边的时候如果删掉的是桥从而导致图的不联通,所以在克鲁斯卡尔中需要利用并查集统计图中的联通分量的个数,如果整个图不联通返回无穷大,使权值本次更新一定失败。

复杂度O(n-1*优化后的克鲁斯卡尔的复杂度)


#include<iostream>//导入输入输出流头文件
#include<algorithm>//导入头文件
using namespace std;//导入标准命名空间
typedef struct edge//定义边的结构体
{
    int v,w,d;//定义一条边的两个端点及其边长
    bool is_delete;//定义是否被删除标志
} Edge;
#define MAXN 200007//定义最大边长
#define MAXNUM 0x3f3f3f3f//定义无穷大值
Edge bian[MAXN];//建立边的数组
int pre[MAXN];//定义集合父节点数组
int n,e;//定义边的数目和点的数目
int MST_id[MAXN];//定义树的下标保存数组
int mid_i;//生成树数组的下标
int cnt;//定义图内连同分量的个数
bool cmp(Edge a,Edge b)
{
    return a.d<b.d;
}
int Find(int x)//压缩路径
{
    int temp,p=x;
    while(x!=pre[x])
    x=pre[x];//先找到x所属集合的根节点
    while(p!=x)//更新路径上的根节点全部压缩
    {
        temp=pre[p];//从x到根节点路径遍历
        pre[p]=x;
        p=temp;
    }
    return x;//返回该集合的根节点
}
int kruskal(int p)//进入克鲁斯卡尔算法
{
    int sum=0;//定义最小生成树权值和sum
    for(int i=0;i<e;i++)//遍历每条边
    {
        int p1=Find(bian[i].v);
        int p2=Find(bian[i].w);
        if(bian[i].is_delete==false&&p1!=p2)//如果这条边没有被标记删除,并且两个点集不一个父节点
        {
            cnt--;//集合数目减一个
            pre[p1]=pre[p2];//父节点改变
            sum+=bian[i].d;//权值和加这条边的权值
            if(p==1)//如果是第一次生成
            {
                MST_id[mid_i++]=i;//记录最小生成树的边的id
            }
            if(cnt==1)
                break;
        }
    }
    if(cnt==1)//如果最后全图联通返回权值和
    return sum;
    else//否则返回无穷大
        return MAXN;
}
void init()//初始化父节点和集合个数cnt
{
    for(int i=0;i<=n;i++)
        pre[i]=i;
        cnt=n;
}
int main()
{
    ios::sync_with_stdio(false);//优化输入输出
    int ncase;//定义测试组数
    cin>>ncase;
    while(ncase--)
    {
        cin>>n>>e;//给定顶点数和边数
       mid_i=0;
        init();//初始化
        for(int i=0; i<e; i++)
        {
            int v,w,d;
            cin>>v>>w>>d;
            bian[i].d=d;
            bian[i].v=v;
            bian[i].w=w;//建图
            bian[i].is_delete=false;
        }
        sort(bian,bian+e,cmp);//排序
        int sum=kruskal(1);//获得最小权值并标记最小生成树的边
         int tsum=MAXNUM;
        for(int i=0;i<mid_i;i++)
        {
            //cout<<mid_i<<endl;
            init();//每次初始化
            bian[MST_id[i]].is_delete=true;//把最小生成树的一条边删除
            tsum=min(kruskal(0),tsum);//更新最小值
            bian[MST_id[i]].is_delete=false;//恢复这条边
        }
        if(tsum==sum)//如果数目大于0,就存在
        {
            cout<<"Yes"<<endl;
        }
        else
            cout<<"No"<<endl;
    }
}
 
  

第二种是借助普里找不在树中最大边看看原图中有没有与之等价的,有就说明不唯一

O(n^2)

#include<iostream>//导入输入输出流函数
#include<string.h>//用到memset
using namespace std;//导入标准命名空间
#define MAXN 505//定义最大的定点数
#define MAXNUM 0x3f3f3f3f//定义无穷大
int vis[MAXN];//访问标记数组
int graph[MAXN][MAXN];//矩阵图
int n,e;//顶点数,边数
int low[MAXN];//保存已经建好的树的顶点到各个点的最小值
int MST[MAXN][MAXN];//i到j路径上的最小值
int is_MST[MAXN][MAXN];//是否在树中
int pre[MAXN];//确定的那一点的边上的另一个节点
int csum=MAXNUM;
int sum=0;
void init()
{
    memset(graph,0x3f,sizeof(graph));//初始化图
    for(int i=0;i<=n;i++)
        graph[i][i]=0;//将自己和自己设置成0
      memset(MST,0,sizeof(MST));//初始化为0
    memset(is_MST,0,sizeof(is_MST));//初始化为0
csum=MAXNUM;
 sum=0;
}
bool prim(int s)//prim算法
{


    for(int i=1;i<=n;i++)
    {
        low[i]=graph[s][i];//每个点到s的距离
        pre[i]=s;//每个点的直接前驱是s
        vis[i]=0;//初始化每个点都没有被访问过
    }
    vis[s]=1;//把s点设置访问过
    for(int j=1;j<n;j++)//循环n-1次
    {
        int tmin=MAXN;//
        int fa=0;//
        int ti=-1;
        for(int i=1;i<=n;i++)
        {
            if(vis[i]==0&&low[i]<tmin)
            {
                tmin=low[i];
                ti=i;
            }
        }//找到最小的low以及下标
        sum+=tmin;
            vis[ti]=1;//设置为已经找到
            fa=pre[ti];//取出ti这个点所在边的另一个点
            is_MST[ti][fa]=is_MST[fa][ti]=1;//将这条边标记为在树中
            for(int i=1;i<=n;i++)
            {
                if(vis[i]==1&&i!=ti)
                {
                    MST[i][ti]=MST[ti][i]=max( MST[i][fa],low[ti]);//更新最大的从其他已经在树中点到刚找的点的路径上的最大边
                }
                if(!vis[i]&&low[i]>graph[ti][i])//如果没有被访问过,s松弛low
                {
                    low[i]=graph[ti][i];
                    pre[i]=ti;
                }
            }
        }

        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<i;j++)
            {
                if(is_MST[i][j]==0&&MST[i][j]!=MAXNUM)//如果有边并且没在树里面
                {
                     //res=min(res,mst-path[i][j]+g[i][j]); 求具体的csum
                     csum=min(csum,sum-MST[i][j]+graph[i][j]);//求次小生成树
                    if(graph[i][j]==MST[i][j])//如果在原图中有等价的最大边则一定存在
                    {
                        //return true;
                    }
                }
            }
        }
        return false;
}
int main()
{
    ios::sync_with_stdio(false);
    int ncase;
    cin>>ncase;
   // ios::sync_with_stdio(false);
    while(ncase--)
    {
        cin>>n>>e;
        init();
        for(int i=0;i<e;i++)
        {
            int v,w,d;
            cin>>v>>w>>d;
            graph[v][w]=d;
            graph[w][v]=d;
          //  cout<<i<<endl;
        }//建图
prim(1);
    if(sum==csum)//如果原来的和新建的一样,那么就输出yes
    {
        cout<<"Yes"<<endl;
    }
    else
    {
        cout<<"No"<<endl;
    }
    }
    return 0;
}
 
 

还是那句话点少用普里母

点多用克鲁斯卡尔





猜你喜欢

转载自blog.csdn.net/memeda1141/article/details/79936546