URAL 1416 Confidential (最小生成树+次小生成树)
Description
Zaphod Beeblebrox — President of the Imperial Galactic Government. And by chance he is an owner of enterprises that trade in secondhand pens. This is a complicated highly protable and highly competitive business. If you want to stay a leader you are to minimize your expenses all the time. And the presedent's high post helps in those aairs. But he is to keep this business in secret. As a president Zaphod has access to the top secret and important information an exact value of power loss in the hyperspace transition between the planets. Of course, this information is very useful to his company. Zaphod is to choose the minimal possible set of trans-planet passages so that he could pass from any planet to any other one via those passages and their total cost would be minimal. The task won't be complicated if Zaphod was not to keep in secret that he helps his company with the secret information. Thus, Zaphod decided to find not the cheapest passages set but the next one. As a real businessman he wants to estimate the value of his conspiracy expenses.
Input
The first input line contains two integers: N (2 ≤ N ≤ 500) is a number of planets in the Galaxy and M is an amount of possible transitions. The next M lines contain three integers ai, bi the numbers of the planets that are connected with some passage (1 ≤ ai, bi ≤ N), and wi (0 ≤ wi ≤ 1000) is the transition cost. If an A to B transition is possible then a B to A transition is possible, too. The cost of those transitions are equal. There is not more than one passage between any two planets. One can reach any planet from any other planet via some chain of these passages.
Output
You should find two different sets of transitions with the minimal possible cost and output theirs costs. Print the minimal possible cost first. If any of those sets of transitions does not exist denote it's cost by ?1.
Sample Input
4 6
1 2 2
2 3 2
3 4 2
4 1 2
1 3 1
2 4 1
3 2
1 2 2
2 3 2
input output
Cost: 4
Cost: 4
Cost: 4
Cost: -1
分析:
首先了解一下关于次小生成树的解法:
给出一个带边权的无向图G,设其最小生成树为T,求出图G的与T不完全相同的边权和最小的生成树(即G的次小生成树)。一个无向图的两棵生成树不完全相同,当且仅当这两棵树中至少有一条边不同。注意,图G可能不连通,可能有平行边,但一定没有自环(其实对于自环也很好处理:直接舍弃。因为生成树中不可能出现自环)。
关于这道题可以这样来分析:
定义生成树T的一个可行变换(-E1, +E2):将T中的边E1删除后,再加入边E2(满足边E2原来不在T中但在G中),若得到的仍然是图G的一棵生成树,则该变换为可行变换,该可行变换的代价为(E2权值 - E1权值)。这样,很容易证明,G的次小生成树就是由G的最小生成树经过一个代价最小的可行变换得到。进一步可以发现,这个代价最小的可行变换中加入的边E2的两端点如果为V1和V2,那么E1一定是原来最小生成树中从V1到V2的路径上的权值最大的边。
这样,对于本题就有两种算法了:(以下的T全部指G的最小生成树)
(1)Prim:
设立数组len,len[x][y]表示T中从x到y路径上的最大边的权值。
len数组可以在用prim算法求最小生成树的过程中得出。每次将边(i, j)加入后(j是新加入树的边,i=pre[j]),枚举树中原有的每个点k(包括i,但不包括j),则len[k][j]=max{len[i][j], (i, j)边权值},又由于len数组是对称的,可以得到len[j][k]=len[k][j]。
然后千万记住将图G中的边(i, j)删除(就是将邻接矩阵中(i, j)边权值改为∞)
因为T中的边是不能被加入的。等T被求出后,所有的len值也求出了,然后,枚举点i、j,若邻接矩阵中边(i, j)权值不是无穷大(这说明i、j间存在不在T中的边),则求出{(i, j)边权值 -lenF[i][j]}的值,即为加入边(i, j)的代价,求最小的总代价即可。
另外注意三种特殊情况:
【1】图G不连通,此时最小生成树和次小生成树均不存在。判定方法:在扩展T的过程中找不到新的可以加入的边;
【2】图G本身就是一棵树,此时最小生成树存在(就是G本身)但次小生成树不存在。判定方法:在成功求出T后,发现邻接矩阵中的值全部是无穷大;
【3】图G存在平行边。这种情况最麻烦,因为这时代价最小的可行变换(-E1, +E2)中,E1和E2可能是平行边
因此,只有建立两个邻接矩阵,分别存储每两点间权值最小的边和权值次小的边的权值,然后,每当一条新边(i, j)加入时,不是将邻接矩阵中边(i, j)权值改为无穷大,而是改为连接点i、j的权值次小的边的权值。
#include<stdio.h>
#include<iostream>
#include<string.h>
const int INF=0x3f3f3f3f;
using namespace std;
int n,m;
int tu[505][505];//保存在原始的图信息
int len[505][505];//保存每两点咋最小生成树上路径中最长边长
int dis[505];//距离
int pre[505];//最小生成树里面这个点的前驱节点
void init()//数据初始化
{
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
tu[i][j]=INF;
len[i][j]=0;
}
}
}
void prim()
{
dis[1]=-1;
for(int i=2; i<=n; i++) //初始化最小生成树里的路径长度和前驱节点
{
dis[i]=tu[1][i];
pre[i]=1;
}
int sum=0,Min,k;
for(int i=2; i<=n; i++)//进行n-1次循环
{
Min=INF;
k=0;
for(int j=1; j<=n; j++)//找出当前的最小边
{
if(dis[j]!=-1&&Min>dis[j])
{
Min=dis[j];
k=j;
}
}
if(k==0) break;
tu[k][pre[k]]=tu[pre[k]][k]=INF;//把最小生成树上的这条边删去
for(int j=1; j<=n; j++)
{
if(dis[j]==-1)//这个点已经加入到最小生成树里面
{
//如果此时多加一条k到j的边,这样的话肯定就形成了一个环,我们要找出这个环里面的最大值
len[k][j]=len[j][k]=max(Min,len[pre[k]][j]);
}
}
dis[k]=-1;
sum+=Min;
for(int j=1; j<=n; j++)
{
if(dis[j]!=-1&&dis[j]>tu[k][j])
{
dis[j]=tu[k][j];
pre[j]=k;
}
}
}
printf("Cost: %d\n",sum);
Min=INF;
bool flag=0;
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
if(tu[i][j]!=INF&&Min>tu[i][j]-len[i][j])//这条边被其他的边替代过后的距离差
{
Min=tu[i][j]-len[i][j];
flag=1;
}
if(!flag) printf("Cost: -1\n");
else printf("Cost: %d\n",sum+Min);
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
init();
int a,b,w;
for(int i=0; i<m; i++)
{
scanf("%d%d%d",&a,&b,&w);
tu[a][b]=tu[b][a]=w;
}
prim();
}
return 0;
}
2)Kruskal:
Kruskal算法也可以用来求次小生成树。在准备加入一条新边(a, b)(该边加入后不会出现环)时,选择原来a所在连通块(设为S1)与b所在连通块(设为S2)中,点的个数少的那个(如果随便选一个,最坏情况下可能每次都碰到点数多的那个,时间复杂度可能增至O(NM)),找到该连通块中的每个点i,并遍历所有与i相关联的边,若发现某条边的另一端点j在未选择的那个连通块中(也就是该边(i, j)跨越了S1和S2)时,就说明最终在T中"删除边(a, b)并加入该边"一定是一个可行变换,且由于加边是按照权值递增顺序的,(a, b)也一定是T中从i到j路径上权值最大的边,故这个可行变换可能成为代价最小的可行变换,计算其代价为[(i, j)边权值 - (a, b)边权值],取最小代价即可。
注意,在遍历时需要排除一条边,就是(a, b)本身(具体实现时由于用DL边表,可以将边(a, b)的编号代入)。另外还有一个难搞的地方:如何快速找出某连通块内的所有点?方法:由于使用并查集,连通块是用树的方式存储的,可以直接建一棵树(准确来说是一个森林),用“最左子结点+相邻结点”表示,则找出树根后遍历这棵树就行了,另外注意在合并连通块时也要同时合并树。
对于三种特殊情况:
【1】图G不连通。判定方法:遍历完所有的边后,实际加入T的边数小于(N-1);
【2】图G本身就是一棵树。判定方法:找不到这样的边(i, j);
【3】图G存在平行边。这个对于Kruskal来说完全可以无视,因为Kruskal中两条边只要编号不同就视为不同的边。
其实Kruskal算法求次小生成树还有一个优化:每次找到边(i, j)后,一处理完这条边就把它从图中删掉,因为当S1和S2合并后,(i, j)就永远不可能再是可行变换中的E2了。
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
const int INF=0x3f3f3f3f;
using namespace std;
int n,m;
int fa[505];//并查集数组
int findRoot(int x) //递归查找顶点x所在树的根
{
if (fa[x] == x) return x;//若为x,则x的根就是自身
else //否则
{
int tmp =findRoot(fa[x]);//递归查找x的父亲Tree[x]的根,tmp为最终的树根
fa[x]= tmp; //查找过程中进行路径压缩:把x到根之间遇到的所有顶点的父亲设为tmp
return tmp; //返回树根
}
}
struct Node//存储边信息
{
int a,b,w;
bool select;//标记是否在最小生成树中
} edge[250009];
bool cmp(Node A,Node B)
{
if(A.w!=B.w)
return A.w<B.w;
if(A.a!=B.a)
return A.a<B.a;
return A.b<B.b;
}
//链式前向星的数据结构
struct Node1
{
int to;
int next;
};
Node1 link[505];//边数组
int Count;//边数组中数据的个数
int head[505];//邻接表的头结点位置
int End[505];//邻接表的伪结点位置
int len[505][505];//每两点在最小生成树上路径中最长边长
void init()//初始化
{
for(int i=1; i<=n; i++)
{
head[i]=-1;
fa[i]=i;
}
}
void kruskal()
{
int k=0;//加入到最小生成树里的边的数目
//初始化邻接表,对于每个节点添加一条指向其自身的边,表示以i为代表元的集合只有点i
for(Count=1; Count<=n; Count++)
{
link[Count].to=Count;
link[Count].next=head[Count];
End[Count]=Count;
head[Count]=Count;
}
for(int i=1; i<=m; i++)
{
if(k==n-1) break;
int x=findRoot(edge[i].a);
int y=findRoot(edge[i].b);
if(x!=y)//这条边没有加入到生成树里面
{
//修改部分,遍历两个节点所在的集合
for(int w=head[x]; w!=-1; w=link[w].next)
{
printf("w===%d\n",w);
for(int v=head[y]; v!=-1; v=link[v].next)
{
//每次合并两个等价类的时候,分别属于两个等价类的两个点间的最长边一定是当前加入的边
len[link[w].to][link[v].to]=len[link[v].to][link[w].to]=edge[i].w;
}
}
//合并两个邻接表
link[End[y]].next=head[x];
End[y]=End[x];
fa[x]=y;
k++;
edge[i].select=true;
}
}
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
init();
for(int i=1; i<=m; i++)
{
scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].w);
edge[i].select=false;
}
sort(edge+1,edge+1+m,cmp);
kruskal();
int mst=0;
for(int i=1; i<=m; i++)
{
if(edge[i].select)
{
mst+=edge[i].w;
}
}
printf("Cost: %d\n",mst);
int ans=INF;
for(int i=1; i<=m; i++)
{
if(!edge[i].select)
ans=min(ans,mst+edge[i].w-len[edge[i].a][edge[i].b]);
}
if(ans==INF)
ans=-1;
printf("Cost: %d\n",ans);
}
return 0;
}
URAL 1416 Confidential (最小生成树+次小生成树)的更多相关文章
- URAL 1416 Confidential(次小生成树)
题目链接:http://acm.timus.ru/problem.aspx?space=1&num=1416 Zaphod Beeblebrox — President of the Impe ...
- URAL 1416 Confidential --最小生成树与次小生成树
题意:求一幅无向图的最小生成树与最小生成树,不存在输出-1 解法:用Kruskal求最小生成树,标记用过的边.求次小生成树时,依次枚举用过的边,将其去除后再求最小生成树,得出所有情况下的最小的生成树就 ...
- 训练指南 UVALive - 5713(最小生成树 + 次小生成树)
layout: post title: 训练指南 UVALive - 5713(最小生成树 + 次小生成树) author: "luowentaoaa" catalog: true ...
- [ An Ac a Day ^_^ ] [kuangbin带你飞]专题八 生成树 UVA 10600 ACM Contest and Blackout 最小生成树+次小生成树
题意就是求最小生成树和次小生成树 #include<cstdio> #include<iostream> #include<algorithm> #include& ...
- POJ 1679 The Unique MST 【最小生成树/次小生成树模板】
The Unique MST Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 22668 Accepted: 8038 D ...
- 最小生成树&&次小生成树
对于一个边上具有权值的图来说,其边权值和最小的生成树叫做图G的最小生成树 求无向图最小生成树主要有prim和kruskal两种算法 1.prim 将点集V分成Va和Vb两部分,Va为已经连入生成树的点 ...
- 最小生成树(次小生成树)(最小生成树不唯一) 模板:Kruskal算法和 Prim算法
Kruskal模板:按照边权排序,开始从最小边生成树 #include<algorithm> #include<stdio.h> #include<string.h> ...
- HDU 4081 Qin Shi Huang's National Road System(最小生成树/次小生成树)
题目链接:传送门 题意: 有n坐城市,知道每坐城市的坐标和人口.如今要在全部城市之间修路,保证每一个城市都能相连,而且保证A/B 最大.全部路径的花费和最小,A是某条路i两端城市人口的和,B表示除路i ...
- (最小生成树 次小生成树)The Unique MST -- POJ -- 1679
链接: http://poj.org/problem?id=1679 http://acm.hust.edu.cn/vjudge/contest/view.action?cid=82831#probl ...
随机推荐
- 在sql server ide里数据修改数据
在sql server 的客户端工具ssms里,只有在工具里打开后直接修改. 除了用这种方法外,还有其它方法可以改吗?比如像pl/sql里的for update sql server的客户端功能比较差 ...
- 深入理解JAVA虚拟机阅读笔记1——JAVA内存区域
一.Java内存区域 1.程序计数器 线程私有. 当前线程所执行的字节码的行号指示器.由于JAVA是多线程的,因此每个线程都独立的程序计数器. 异常:没有规定任何OutOfMemeryError情况的 ...
- DAY2-Flask项目
回顾: 1.安装pipenv虚拟运行环境,隔离项目 (启动:pipenv shell) 2.安装flask(pipenv install shell),查看项目依赖(pipenv graph) 3.查 ...
- Avito Cool Challenge 2018 自闭记
A:n==2?2:1. #include<iostream> #include<cstdio> #include<cmath> #include<cstdli ...
- P3850 [TJOI2007]书架
题目描述 Knuth先生家里有个精致的书架,书架上有N本书,如今他想学到更多的知识,于是又买来了M本不同的新书.现在他要把新买的书依次插入到书架中,他已经把每本书要插入的位置标记好了,并且相应的将它们 ...
- P3919 【模板】可持久化数组(可持久化线段树/平衡树)
题目描述 如题,你需要维护这样的一个长度为 N 的数组,支持如下几种操作 在某个历史版本上修改某一个位置上的值 访问某个历史版本上的某一位置的值 此外,每进行一次操作(对于操作2,即为生成一个完全一 ...
- 单点登录(十七)----cas4.2.x登录mongodb验证方式成功后返回更多信息更多属性到客户端
我们在之前已经完成了cas4.2.x登录使用mongodb验证方式登录成功了.也解决了登录名中使用中文乱码的问题. 单点登录(十三)-----实战-----cas4.2.X登录启用mongodb验证方 ...
- 团体程序设计天梯赛 L2-006. 树的遍历 L2-011. 玩转二叉树
L2-006. 树的遍历 #include <stdio.h> #include <stdlib.h> #include <string.h> #include & ...
- UIImage加载图片的方式以及Images.xcassets对于加载方法的影响
UIImage加载图片的方式以及Images.xcassets对于加载方法的影响 图片缓存 根据是否将创建好的对象缓存入系统内存,有两类创建UIImage对象的方法可选: 缓存:+ imageName ...
- 安装JDK、Tomcat、Maven’详细步骤
安装JDK 1.首先在官网下载JDK1.8包并解压(随便你装哪个版本) 2.下面开始配置环境变量 此电脑-右键-属性-高级系统配置 点击环境变量 将会看到以下界面 在系统变量下“新建” 变量名(J ...