SPFA算法 poj 1201Intervals


在做差分约束时,碰到了SPFA算法,它是Bellman_ford的改进版。。。

觉得Bellman_ford写着很简单,相当简单,无非分三步,
第一步:初始化,这一步往往把dis[i] = MAX;而把源点s的dis[s] = 0;
第二步:迭代求解,反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点v的最短距离估计值逐步逼近其最短距离。一般是两重循环,for(int i = 1; i < n; i++)            for(int j = 1; j <= edgenum; j++)
n为节点个数,第一重循环是除了源点s外的n-1个,第二重中,edgenum为边的个数,它的确定一般是在输入处理时
循环里边就是那个三角不等式了,if(dis[v] > dis[u] + w)  .................再么,加个flag标志位,当一次j 从1到edgenum,if条件都未成立,则可以跳出循环。

第三步:检验是否有负环回路,就是看经过第二步后,是否还能松弛。。。

参考我博客另一篇:http://blog.csdn.net/bill_ming/article/details/7628435

很好的参考:http://www.wutianqi.com/?p=2285

 for(int j = 1; j <= edgenum; j++)
{
if(dis[v] > dis[u] + w) 
return false;
}
看图理解:


可以看出,dijkstra不能求带负权值的最短路,而Bellman_ford不仅可以求带负权值的最短路,而且还可以判断是否有负环回路,但是。。。。。。效率很低啊。。。O(顶点数*边数)

原因在于,(不知道表述的对不?)更新每个节点时,都要更新源点到每条边的长度,所以源点到每个顶点的最短长度只有在最后一松弛才能确定,也因此才能计算带有负权值的情况,但其实,每次松弛,有的是没改变的,因此产生了很多冗余计算,所以才有了改进版---SPFA

SPFA对Bellman_ford的优化关键是:只有那些在前一遍松弛中改变距离估计值的点,才可能引起他们邻接点的估计值改变,因此,SPFA用一个队列存放被成功松弛的顶点

大致流程:维护一个队列,初始时将源加入队列,每次取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则入队。直到队列为空,结束。

有一个问题:一个点如果被改变多次,也就是一个点改进过其它的点之后,过了一段时间可能本
身被改进,于是再次用来改进其它的点,这样反复迭代下去。所以我们可以设置一个bool vis[ ],来记录该节点是否入队,我们每次仅将满足条件的未入队的顶点加入队列。。。
但还有一个问题:如果有负环回路,怎么办?那就会反复运行,无穷减小,队列不会为空了
这是怎么办?思考Bellman-Ford算法,它是如何结束的?显然,最朴素的Bellman-Ford算法不管循环过程中发生了什么,一概要循环|V|-1遍才肯结束。凭直觉我们可以感到,SPFA算法“更聪明一些”,就是说我们可以猜测,假如在SPFA中,一个点进入队列——或者说一个点被处理——超过了|V|次,那么就可以断定图中存在负权回路了。

还有一个问题:如果我们要输出最短路径本身怎么办。。。

我们可以用一个path[ ]数组,path[i]表示从S到i的最短路径中,i节点之前的编号。我们再借助节点u对v进行松弛时,标记下path[v] = u;记录就完成了。。。输出时的问题:因为我们记录的是前面的点是什么,输出却要从后边。

void PrintPath(int k)

{

if( path[k] )

 PrintPath( path[k] );

cout << k << " ";

}


题目链接:http://poj.org/problem?id=1201

觉得,差分约束,最重要的是找到约束条件,也就是那些不等式,包括给出的,隐含的。。。

然后经过输入时处理后,用Bellman_ford,SPFA,不过要灵活运用,不能死板模板。。。做题还是太少了。。。

题意:给了我们一些区间,然后告诉每个区间中至少需要取Ci个数。求出满足n个条件的集合Z的最少的元素个数。

不等式:

s[b+1]-s[a] >= c; 已给

1>=s[i+1]-s[i]>=0 隐含

我邻接表没怎么用过,都是用邻接矩阵。。。。多做题啊。。

如果发现有错,或者与您想法不符,求留言探讨。。。

//1201	Accepted	3392K	1188MS	C++	1916B
#include <iostream>
#include <cstdio>
#include <string>
#include <queue>
#include <cstring>
using namespace std;
const int M = 151010;
const int INF = -9999999;

struct Edge   //邻接表
{
    int v;
    int w;
    int next;  //
} edge[M];
int dis[M];
int head[M];  //保存边的头结点编号
bool visit[M];
int num,n,maxx,minn;
int number ;
queue<int> p;
int SPFA()
{
    int e;
    for(int i = minn; i <= maxx; i++)
        dis[i] = INF;
    dis[minn] = 0;
    p.push(minn);//源点入队
    visit[minn] = true; //标记在队中
    while( !p.empty() )
    {
        e = p.front();
        p.pop();
        visit[e] = false; //标记不在队中
        for(int i = head[e]; i != -1; i = edge[i].next)
        {
            if (dis[edge[i].v] < dis[e] + edge[i].w)   //找最长路,三角不等式
            {
                dis[edge[i].v] = dis[e] + edge[i].w;
                if( !visit[edge[i].v] )
                {
                    visit[edge[i].v] = true;
                    p.push(edge[i].v);
                }
            }

        }

    }
    return dis[maxx];
}
int main()
{

    int a,b,c;
    num = 0;
    number = 1;
    maxx = -9999;
    minn = 9999;
    memset(dis,0,sizeof(dis));
    memset(head,-1,sizeof(head));  //不要写0啊啊啊啊
    memset(visit,false,sizeof(visit));
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> a >> b >> c;

        edge[num].next = head[a];
        edge[num].v = b + 1;
        edge[num].w = c;
        head[a] = num;
        num ++;
        if(maxx < b+1)
            maxx = b+1;
        if(minn > a)
            minn = a;

    }
    for(int i = minn; i < maxx; i++)
    {

        edge[num].next = head[i];
        edge[num].v = i+1;
        edge[num].w = 0;
        head[i] = num;
        num++;
        edge[num].next = head[i+1];
        edge[num].v = i;
        edge[num].w = -1;
        head[i+1] = num;
        num++;
    }
    int ans = SPFA();
    cout << ans <<endl;
    return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值