C 信道安全

信道安全

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 2
描述
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;
}



转载于:https://www.cnblogs.com/xunalove/p/6809154.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值