信道安全
-
描述
-
Alpha 机构有自己的一套网络系统进行信息传送。情报员 A 位于节点 1,他准备将一份情报 发送给位于节点 n 的情报部门。可是由于最近国际纷争,战事不断,很多信道都有可能被遭到监 视或破坏。 经过测试分析,Alpha 情报系统获得了网络中每段信道安全可靠性的概率,情报员 A 决定选 择一条安全性最高,即概率最大的信道路径进行发送情报。 你能帮情报员 A 找到这条信道路径吗?
-
输入
-
第一行: T 表示以下有 T 组测试数据 ( 1≤T ≤8 )
对每组测试数据:
第一行:n m 分别表示网络中的节点数和信道数 (1<=n<=10000,1<=m<=50000)
接下来有 m 行, 每行包含三个整数 i,j,p,表示节点 i 与节点 j 之间有一条信道,其信
道安全可靠性的概率为 p%。 ( 1<=i, j<=n 1<=p<=100)
输出
-
每组测试数据,输出占一行,一个实数 即情报传送到达节点 n 的最高概率,精确到小数点后
6 位。
样例输入
-
1 5 7 5 2 100 3 5 80 2 3 70 2 1 50 3 4 90 4 1 85 3 1 70
样例输出
-
61.200000
来源
-
河南省第九届省赛
看到这道题第一感觉就是这是一道最短路类型的题目,因为求的是最大安全概率,dijstra的变形:
结果不仅超时还内存不足,我天难道不是这样写的吗???#include<iostream> #include<string> #include<algorithm> #include<memory.h> #include<stdio.h> using namespace std; int n,m;//n代表节点数 m代表信道数 double map1[10001][10001]; double vis[50005]; double dis[50005]; double dijstra(int x,int y) { int i,j,k; double maxn=-1; for(i=1;i<=n;i++) { dis[i]=map1[x][i]; vis[i]=0; } vis[i]=1; for(i=0;i<n;i++) { maxn=-1; for(j=1;j<=n;j++) { if(vis[j]==0&&dis[j]>maxn) { maxn=dis[j]; k=j; } } vis[k]=1; for(j=1;j<=n;j++) { if(vis[j]==0) { if(dis[j]<dis[k]*map1[k][j]) { dis[j]=dis[k]*map1[k][j]; } } } } printf("%.6lf\n",dis[n]); return 0; } int main() { int num; cin>>num; while(num--) { int x,y; double p; cin>>n>>m; memset(map1,0,sizeof(map1));//初始化为比例为1 for(int i=0;i<m;i++) { cin>>x>>y>>p; map1[x][y]=map1[y][x]=p/100; // cout<<p/100<<endl; } dijstra(1,n); } }
接着我百度别人的题解,大概都是说要用SFPA算法写,可是我不懂,别人的题解也看不到。
决定先学习SFPA算法。(学习这个算法有四五天才真正搞明白)
spfa算法
求单源最短路的spfa算法全称:Shortest Path Faster Algorithm。
很多时候,给定的图存在负边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。有人称SPAFA算法是最短路的万能算法。
SPFA的算法思想(动态逼近法):
数组dis:记录每个结点的最短路径值,可以用邻接矩阵或邻接表来存储图G,推荐使用邻接表。
队列q:设立一个先进先出的队列q用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放在队列中,这样不断从队列中取出结点进行松弛操作,直至队列空为止。
(松弛操作的原理是著名的定理:“三角形两边之和大于第三边”,在信息学中我们叫它三角形不等式。所谓对结点i,j进行松弛,就是判定是否dis[j]>dis[i]+w[i,j],如果该式成立则将dis[i]+w[i,j],否则不动。
下面是我根据这道题目来看sfpa算法是怎么进行的:
图1有向图G,求源点v0到各点的最短路。
q v1 v1 v2 v3 v4 v5 dis 1.0 负无穷 负无穷 负无穷 负无穷 (1)源点入队,dis[v1]=1.0,其余为负无穷
q v2 v3 v4 v1 v2 v3 v4 v5 dis 1 0.5 0.7 0.85 负无穷 (2)源点v1出队,v2,v3,v4入队; dis[v2]=0.5,dis[v3]=0.7,dis[v4]=0.85 q v3 v4 v5 v1 v2 v3 v4 v5 dis 1 0.5 0.7 0.85 0.5 (3)v2出队,v5入队; dis[v5]<dis[v2]*a[v2][v5]
q v4 v5 v1 v2 v3 v4 v5 dis 1 0.5 0.7 0.85 0.56 (4)v3出队,v5入队;(因为v5已经在队列中,不入队) dis[v5]<dis[v3]*a[v3][v5]
q v5 v3 v1 v2 v3 v4 v5 dis 1 0.5 0.765 0.85 0.56 (5)v4出队,v3入队; dis[v3]<dis[v3]*a[v3][v4]
q v3 v1 v2 v3 v4 v5 dis 1 0.5 0.765 0.85 0.56 (6)v5出队
q v2 v5 v1 v2 v3 v4 v5 dis 1 0.53 0.765 0.85 0.612 (7)v3出队,v2,v5进队 dis[v2]<dis[v3]*a[v3][v2] dis[v5]<dis[v3]*a[v3][v5]
q v5 v1 v2 v3 v4 v5 dis 1 0.53 0.765 0.85 0.612 (8)v2出队 dis[v2]<dis[v3]*a[v3][v2]
q v2 v1 v2 v3 v4 v5 dis 1 0.612 0.765 0.85 0.612 (9)v5出队,v2进队 dis[v2]<dis[v5]*a[v5][v2]
q v1 v2 v3 v4 v5 dis 1 0.612 0.765 0.85 0.612 (10)v2出队
q v1 v2 v3 v4 v5 dis 1 0.612 0.765 0.85 0.612 (10)队列空
最短路径本身怎么输出
定义一个path[]数组,path[i]表示从源点s到i的最短路程中,结点i之前的结点的编号(父结点),我们在借助结点u对结点v松弛的同时,标记path[v]=u;
递归输出:
-
void showpath(int k)//输出路径 { if(path[k]!=-1) showpath(path[k]); cout<<k<<" "; }
-
SPFA算法(邻接矩阵)
我天,邻接矩阵肯定会超时。
原来spfa算法还需要优化:
spfa优化-----前向星优化
星形(star)表示法的思想与邻接表表示法的思想有一定的相似之处。对每个结点,它也是记录从该结点出发的所有弧,但它不是采用单向链表而是采用一个单一的数组表示。也就是说,在该数组中首先存放从结点1出发的所有弧,然后接着存放从节点2出发的所有孤,依此类推,最后存放从结点n出发的所有孤。对每条弧,要依次存放其起点、终点、权的数值等有关信息。这实际上相当于对所有弧给出了一个顺序和编号,只是从同一结点出发的弧的顺序可以任意排列。此外,为了能够快速检索从每个节点出发的所有弧,我们一般还用一个数组记录每个结点出发的弧的起始地址(即弧的编号)。在这种表示法中,可以快速检索从每个结点出发的所有弧,这种星形表示法称为前向星形(forward star)表示法。
前向星存储图: -
using namespace std; int first[10005]; struct edge { int point,next,len; } e[10005]; void add(int i, int u, int v, int w) { e[i].point = v; e[i].next = first[u]; e[i].len = w; first[u] = i; } int n,m; int main() { int u,v,w; cin >> n >> m; for (int i = 1; i <= m; i++) { cin >> u >> v >> w; add(i,u,v,w); } //这段是读入和加入 for (int i = 0; i <= n; i++) { cout << "from " << i << endl; for (int j = first[i]; j; j = e[j].next) //这就是遍历边了 cout << "to " << e[j].point << " length= " << e[j].len << endl; } }
结合spfa+前向星优化最终写成的代码为:
-
#include <iostream> #include<memory.h> #include<queue> #include<stdio.h> using namespace std; struct edge { int point,next; double len; } e[50005]; int n, m, s, t; double dis[10005],vis[10005],first[1000005]; int path[10000]; void add(int i, int u, int v, double w) { e[i].point = v; e[i].next = first[u]; e[i].len = w; first[u] = i; } void spfa() { memset(dis,0,sizeof(dis)); memset(vis,0,sizeof(vis)); memset(path,0,sizeof(path)); queue<int>q; for(int i=0;i<=n;i++) { dis[i]=-1; } dis[1]=1.0; vis[1]=1; path[1]=-1; q.push(1); while (!q.empty()) //队列不空 { int v=q.front(); //取队首元素 q.pop(); vis[v]=0; //释放结点,一定要释放掉,因为这节点有可能下次用来松弛其它节点 for (int j = first[v]; j; j = e[j].next) //这就是遍历边了 { if(dis[e[j].point]<dis[v]*e[j].len) { dis[e[j].point]=dis[v]*e[j].len; // path[e[j].point]=v; if(vis[e[j].point]==0) { q.push(e[j].point); vis[e[j].point]=1; } } } } } void showpath(int k)//输出路径 { if(path[k]!=-1) showpath(path[k]); cout<<k<<" "; } int main() { int u,v; double w; int num; cin>>num; while(num--) { cin >> n >> m; int cnt=1; memset(first,0,sizeof(first)); for (int i = 1; i <= m; i++) { cin >> u >> v >> w; add(cnt++,u,v,w/100); add(cnt++,v,u,w/100); } //这段是读入和加入 spfa(); if (dis[n]!=0) printf("%.6lf\n",dis[n]*100); else cout << -1 << endl; //showpath(n); } return 0; }
-
-
第一行: T 表示以下有 T 组测试数据 ( 1≤T ≤8 )