您的位置:首页 > 其它

BellmanFord单源最短路径路径算法差分约束系统ZOJ–2770

2015-06-11 17:47 591 查看
ZOJ-2770 Burn the Linked Camp题目的大意为:

陆逊侦查得知刘备将自己的军队分为N个营,从左到右编号为1,2,3…N。第i个营有最大士兵数Ci,通过一段时间的观察,陆逊可以估算至少有k个士兵住在第i到第j个营地,最后陆逊必须要估算刘备的军队至少有多少士兵,以便应对。

输入:

有多个测试案例,在每个测试案例的第一行,有两个整数N(0<n<=1000)和M(0 <= M = 10000)。第二行中,有n个整数C1…CN。然后M行,每行有三个整数i,j,k(0 < <= j <= N,0≤k<2 ^ 31),这意味着从i到j营士兵总数量至少是K.

输出:

对于每一个测试案例,在一行一个整数,输出:根据陆逊的观察估算刘备军队最少的士兵数。然而,陆逊估计在输入数据可能很不精确。如果他的估计是不真实的,输出“Bad Estimations”在单一的一行。

解题思路:

题目要求从观察结果中求出刘备军队里士兵的最小值。由于每个营地都有能容纳士兵的最大值,存在不等约束关系,此题可以考虑用差分约束系统解题。

差分约束系统:

如果一个系统由n个变量和m个约束条件组成,其中每个约束条件都形如:

Xj-Xi<=Bk   (i,j∈[1,n],k∈[1,m])


则称其为差分约束系统。

求解差分约束系统:

求解差分约束系统,可以转化成图论的单源最短路径问题,

Xj-Xi<=Bk

Xj<=Xi+Bk


类似最短路径的三角不等式

distance[v]<= distance[u]+weight[u,v]

即distance[v] - distance[u]<=weight[u,v]

因此,以每个变量Xi为结点,对于约束条件

Xj-Xi<=Bk 即 Xj<=Xi+Bk

连接一条边(i,j),权值为Bk

再增加一个源点A,A与所有顶点相连,权值均为0,如果图中存在负环,则无解,否则,从源点到其它顶点的单源最短路径就是问题的一组可行解。

设有N个营地,编号为 i(i=1、2、3…N), K(i)代表第i(i=1、2、3…N)个营地的士兵数加上编号小于i的所有营地的士兵数。

则第j(j=1、2、3…N)个营地的士兵数为K(j)-K(j-1)

若M(j)表示营地j能容纳的最大士兵数,则有不等关系:

K(j)-K(j-1)<=M(j)

变换得

K(j)<=K(j-1)+M(j)

为了避免第一个营地的特殊情况,我们增加一个编号为0且士兵数为0的营地,即K(1)-K(0)=K(1).这个营地只是辅助,对结果不产生影响。

我们设每个编号i的营地为顶点i(i=0,1,2…N),我们添加一个源点,源点到所有其他顶点都有一条有向边,权值为0,不需要保存该点,只是辅助解题,保证图的连通性。则K(j)和K(j-1)分别为从源点到j,j-1两点的最短路径权值,若营地j的士兵数为M(j),则有K(j)<=K(j-1)+M(j),可以从j到j-1连接一条有向边,权值为M(j),问题转化为求从源点到顶点j的单源最短路径。

因为从源点到点j的最短路径必定满足K(j)<=K(j-1)+M(j)

同理,从营地i到j之间至少有X个士兵,则有不等关系:

K(j)-K(i-1)>=X

变换为

K(i-1)<=K(j)-X

于是,从顶点j到顶点i-1连一条有向边,权值为-X,求解源点到j点的最短路径时必定也满足该不等式K(i-1)<=K(j)-X



其中K(1),K(2)…K(N)为差分约束系统的一组解,当答案要求非负时,我们对每个解加上一个常数C便可。

最后源点到点N的最短路径便是问题的解,但实际上士兵数不能为负,因为求最少的士兵总数,我们可以加上一个最小的常数C,使得该组解非负。

当图中存在负权环时,问题无解。因为若存在负权环,不停地走这个环的话,最短路径趋向负无穷,显然不可能存在最短路径。

该题涉及负权边,用Bellman Ford算法求解最短路径较合适。

Bellman Ford算法思想是基于以下事实:

“两个点间如果存在最短路径,那么每个结点最多经过一次。”

也就是说,对n个结点的图,这条路不超过n-1条边。
如果一个结点经过了两个,说明我们走了一个圈:
如果该圈的权为正,显然不划算;

如果是负圈,不存在最短路径;

如果是零圈,去掉不影响最短路径。

路径边数上限为m的最短路径,可以通过由边数上限为m-1时的最短路径“外加一条边”来求,则最多只需要迭代n-1次就可以求出最短路径。

Bellman Ford算法伪代码如下:

//V(G)为图G的顶点集,E(G)为图G的边集
for i=0 to |V(G)|
for 每条边(u,v)属于E(G)//松弛每条边
if(distance[v]>distance[u]+weight[u,v])
distance[v]=distance[u]+weight[u,v]
end for
end for
for 每条边(u,v)属于E(G)//检查是否有负环
if(distance[v]>distance[u]+weight[u,v])
return false
end for
return true


算法时间复杂度O(VE)。

该算法简单容易理解,缺点是时间复杂度较高,这里可以用队列进行优化,本文重点是最短路径算法如何应用在差分约束系统,便不再赘述。

Bellman Ford算法用到松弛技术:

维护一个源点到所有其它点的距离表distance[],松弛一条边(u,v)的过程包括测试我们是否可以通过结点u对目前到v结点的最短路径distance[v]进行修改,如果通过u到达v的路径更短,则更新distance[v]。这样便是保证每两个结点都有

distance[v]<= distance[u]+weight[u,v]

伪代码如下:

if(distance[v]> distance[u]+weight[u,v])//不符合就调整
distance[v] = distance[u]+weight[u,v]


代码:

#include<stdio.h>
#include<vector>
using namespace std;
int *disList;//求最短路径时维护的距离表
struct Edge{//边结构
int start;//起
4000
点
int end;//终点
int weight;//权值
};
vector<Edge> E;//存放边集的容器
vector<Edge>::iterator it;//边的迭代器
bool bellmanford(int N)//求每个顶点到源点的最短路径
{
while(--N)//走N-1 条边
{
for (it = E.begin(); it != E.end();it++)//对每条边进行松弛
{
if (it->weight + disList[it->start]< disList[it->end])
{
disList[it->end] = it->weight + disList[it->start];
}
}
}
for (it = E.begin(); it != E.end(); it++)//若还能进行松弛,说明存在负环,无解
{
if (it->weight + disList[it->start]< disList[it->end])
{
return false;
}
}
return true;
};
void output(int N)
{
if (!bellmanford(N))printf("Bad Estimations\n");
else//根据实际情况,兵营士兵数不能为负,将所有解化为正数
{
int min = disList[0];
for (int i = 1; i <= N; i++)
{
if (min > disList[i])min = disList[i];
}
if (min < 0)
{
disList
= disList
- min;
}
printf("%d\n", -disList
);
}
};
int main()
{
int N, M;
Edge e;
while (scanf("%d%d", &N,&M) ==2)
{
E.clear();//清空容器
disList = new int[N + 1];
for (int i = 0; i <= N;i++)//初始化距离表
disList[i]=0;
//数据输入
for (int i = 1; i <= N; i++)
{
scanf("%d", &e.weight);
e.start = i - 1;
e.end = i;
E.push_back(e);//营地i的最大士兵数为w,向矩阵图插入由i-1指向i权值为w的有向边
}
int a, b,c;
for (int i = 0; i < M; i++)
{
scanf("%d%d%d", &a,&b,&c);
e.start = b;
e.end = a - 1;
e.weight = -c;
E.push_back(e);//营地a与营地b之间至少士兵数为c,向矩阵图中插入由点b指向点a,权值为-c的有向边
}
output(N);//输出结果
delete[]disList;//释放内存
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息