转自:http://blog.csdn.net/pi9nc/article/details/12421417
差分约束系统:
如果一个系统由n个变量和m个约束条件组成,其中每个约束条件形如xj-xi<=bk(i,j∈[1,n],k∈[1,m]),则称其为差分约束系统(system of difference constraints)。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。
差分约束可以转化话单源最短路求解。因为单源最短路径满足三角不等式d[v] <= d[u] + w(u, v), 这里的 <= 可以为改 >= 只要改变一下初始化条件即可。
差分约束题目有两种,一种求最大值,另外一种求最小值。
(1)当题目是求满足给定不等式的最小值时,就是求图的最长路。
建图方式如下:a – b >= c,对应于边b –> a w(b, a) = c, 然后求最短路;判断条件是:d[v] <= d[u] + w(u, v), 初始化d[]为-INF. 这样求出的d[]就是满足条件的最小值。原因很简单,因为d[i] 是从-INF开始增大,根据不等式逐渐增大,当满足所有不等式时,那么d[i]肯定是最小的了。
(2)当题目是求满足给定不等式的最大值时,就是求图的最短路。
建图方式如下:a – b <= c,对应于边b –> a w(b, a) = c, 然后求最长路;判断条件是:d[v] >= d[u] + w(u, v), 初始化d[]为INF.这样求出的d[]就是最大值。原因和上面一样,因为是从INF逐渐减小的,当满足完所有条件时,就停止。那么d[i]是最大的了。
常见单源最短路径方法有bellman_ford, spfa, Dijkstra
bellman_ford算法,适用于边值负权的图,还能判断是否存在负权回路,它是把所有的边都松弛n-1次。算法复杂度为O(VE),这个方法效率不高,在数据规模大时,往往会TLM。
spfa算法,是bellman_ford算法的改进版。它通过一个队列或栈,来保存需要更新的顶点。它减去了bellman算法很多不必要的松弛,因此它的效率比bellman要好,为O(ke)。
Dijkstra算法,它只适用于边值为非负的图上。它基于贪心,每次选取最短的边。可以用heap来优化,在正权图也可以考虑。
体会:虽然bellman_ford不及spfa的效率,但它在判断是否存在负权回路比spfa方便。它能判断不连通的图,而spfa判断不连通图时有可能不准确,当开始的源点到达不了某些顶点时,那么那些顶点就无法检验了。为此,spfa需要添加一个超源点来使得图连通(还有一个方法:就是先把所有顶点入队),而bellman不需要添加超源点, 因为它是对图的每一条边都进行n-1次松弛。有时候超源点不好添加,这时可以考虑用bellman。
还有一点,当适用spfa添加超源点使得图连通(超源点S0 到其他顶点距离都为0)时,用最短路求出的解, 除了满足给定的不等式外,还会满足下列的不等式:
S1 – S0 <= 0
S2 – S0 <= 0
…
Sn – S0 <= 0
也就是说S1 ~ Sn是 < 0 的,这个对不同问题,要具体分析。这些额外满足的不等式,不能与题目要求的矛盾。
转自:http://hi.baidu.com/tx_li/item/1fd076dfb56971f83dc2cbfd
比如有这样一组不等式:
X1 - X2 <= 0
X1 - X5 <= -1
X2 - X5 <= 1
X3 - X1 <= 5
X4 - X1 <= 4
X4 - X3 <= -1
X5 - X3 <= -3
X5 - X4 <= -3
不等式组(1)
全都是两个未知数的差小于等于某个常数(大于等于也可以,因为左右乘以-1就可以化成小于等于)。这样的不等式组就称作差分约束系统。
这个不等式组要么无解,要么就有无数组解。因为如果有一组解{X1, X2, ..., Xn}的话,那么对于任何一个常数k,{X1 + k, X2 + k, ..., Xn + k}肯定也是一组解,因为任何两个数同时加一个数之后,它们的差是不变的,那么这个差分约束系统中的所有不等式都不会被破坏。
差分约束系统的解法利用到了单源最短路径问题中的三角形不等式。即对于任何一条边u -> v,都有:
d(v) <= d(u) + w(u, v)
其中d(u)和d(v)是从源点分别到点u和点v的最短路径的权值,w(u, v)是边u -> v的权值。
显然以上不等式就是d(v) - d(u) <= w(u, v)。这个形式正好和差分约束系统中的不等式形式相同。于是我们就可以把一个差分约束系统转化成一张图,每个未知数Xi对应图中的一个顶点Vi,把所有不等式都化成图中的一条边。对于不等式Xi - Xj <= c,把它化成三角形不等式:Xi <= Xj + c,就可以化成边Vj -> Vi,权值为c。最后,我们在这张图上求一次单源最短路径,这些三角形不等式就会全部都满足了,因为它是最短路径问题的基本性质嘛。
话说回来,所谓单源最短路径,当然要有一个源点,然后再求这个源点到其他所有点的最短路径。那么源点在哪呢?我们不妨自已造一个。以上面的不等式组为例,我们就再新加一个未知数X0。然后对原来的每个未知数都对X0随便加一个不等式(这个不等式当然也要和其它不等式形式相同,即两个未知数的差小于等于某个常数)。我们索性就全都写成Xn - X0 <= 0,于是这个差分约束系统中就多出了下列不等式:
X1 - X0 <= 0
X2 - X0 <= 0
X3 - X0 <= 0
X4 - X0 <= 0
X5 - X0 <= 0
不等式组(2)
对于这5个不等式,也在图中建出相应的边。最后形成的图如下:
图1
图中的每一条边都代表差分约束系统中的一个不等式。现在以V0为源点,求单源最短路径。最终得到的V0到Vn的最短路径长度就是Xn的一个解啦。从图1中可以看到,这组解是{-5, -3, 0, -1, -4}。当然把每个数都加上10也是一组解:{5, 7, 10, 9, 6}。但是这组解只满足不等式组(1),也就是原先的差分约束系统;而不满足不等式组(2),也就是我们后来加上去的那些不等式。当然这是无关紧要的,因为X0本来就是个局外人,是我们后来加上去的,满不满足与X0有关的不等式我们并不在乎。
也有可能出现无解的情况,也就是从源点到某一个顶点不存在最短路径。也说是图中存在负权的圈。这一点我就不展开了,请自已参看最短路径问题的一些基本定理。
其实,对于图1来说,它代表的一组解其实是{0, -5, -3, 0, -1, -4},也就是说X0的值也在这组解当中。但是X0的值是无可争议的,既然是以它作为源点求的最短路径,那么源点到它的最短路径长度当然是0了。因此,实际上我们解的这个差分约束系统无形中又存在一个条件:
X0 = 0
也就是说在不等式组(1)、(2)组成的差分约束系统的前提下,再把其中的一个未知数的值定死。这样的情况在实际问题中是很常见的。比如一个问题表面上给出了一些不等式,但还隐藏着一些不等式,比如所有未知数都大于等于0或者都不能超过某个上限之类的。比如上面的不等式组(2)就规定了所有未知数都小于等于0。
对于这种有一个未知数定死的差分约束系统,还有一个有趣的性质,那就是通过最短路径算法求出来的一组解当中,所有未知数都达到最大值。下面我来粗略地证明一下,这个证明过程要结合Bellman-Ford算法的过程来说明。
假设X0是定死的;X1到Xn在满足所有约束的情况下可以取到的最大值分别为M1、M2、……、Mn(当然我们不知道它们的值是多少);解出的源点到每个点的最短路径长度为D1、D2、……、Dn。
基本的Bellman-Ford算法是一开始初始化D1到Dn都是无穷大。然后检查所有的边对应的三角形不等式,一但发现有不满足三角形不等式的情况,则更新对应的D值。最后求出来的D1到Dn就是源点到每个点的最短路径长度。
如果我们一开始初始化D1、D2、……、Dn的值分别为M1、M2、……、Mn,则由于它们全都满足三角形不等式(我们刚才已经假设M1到Mn是一组合法的解),则Bellman-Ford算法不会再更新任合D值,则最后得出的解就是M1、M2、……、Mn。
好了,现在知道了,初始值无穷大时,算出来的是D1、D2、……、Dn;初始值比较小的时候算出来的则是M1、M2、……、Mn。大家用的是同样的算法,同样的计算过程,总不可能初始值大的算出来的结果反而小吧。所以D1、D2、……、Dn就是M1、M2、……、Mn。
那么如果在一个未知数定死的情况下,要求其它所有未知数的最小值怎么办?只要反过来求最长路径就可以了。最长路径中的三角不等式与最短路径中相反:
d(v) >= d(u) + w(u, v)
也就是 d(v) - d(u) >= w(u, v)
所以建图的时候要先把所有不等式化成大于等于号的。其它各种过程,包括证明为什么解出的是最小值的证法,都完全类似。
SPFA代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int MAX=110;
const int INF=(1<<30);
struct node
{
int u,next,f;
}edge[MAX*MAX];
int pre[MAX],dis[MAX],cnt[MAX];
bool vis[MAX];
int N,M,num;
void init()
{
num=0;
memset(pre,-1,sizeof(pre));
}
void add_edge(int x,int y,int f)
{
edge[num].u=y;
edge[num].next=pre[x];
edge[num].f=f;
pre[x]=num++;
}
bool SPFA()
{
for(int i=0;i<=N+1;i++)
{
dis[i]=INF;
vis[i]=false;
cnt[i]=0;
}
dis[N+1]=0;
vis[N+1]=true;
cnt[N+1]=1;
queue<int> q;
q.push(N+1);
while(!q.empty())
{
int t=q.front();
q.pop();
vis[t]=false;
for(int i=pre[t];i!=-1;i=edge[i].next)
{
int v=edge[i].u,w=edge[i].f;
if(dis[v]>dis[t]+w)
{
dis[v]=dis[t]+w;
if(!vis[v])
{
vis[v]=true;
q.push(v);
if(++cnt[v]>N+1)
return true;
}
}
}
}
return false;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
while(cin>>N,N)
{
int s,n,k;
char o[5];
init();
scanf("%d",&M);
for(int i=0;i<M;i++)
{
scanf("%d%d%s%d",&s,&n,o,&k);
if(o[0]=='g')
add_edge(s+n,s-1,-1-k);
else add_edge(s-1,s+n,k-1);
}
for(int i=0;i<=N;i++)
add_edge(N+1,i,0);
if(SPFA())
printf("successful conspiracy\n");
else printf("lamentable kingdom\n");
}
return 0;
}
Bellman代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int MAX=110;
const int INF=(1<<30);
struct node
{
int u,v,next,f;
}edge[MAX*MAX];
int pre[MAX],dis[MAX],cnt[MAX];
bool vis[MAX];
int N,M,num;
void init()
{
num=0;
memset(pre,-1,sizeof(pre));
}
void add_edge(int x,int y,int f)
{
edge[num].u=y;
edge[num].v=x;
edge[num].next=pre[x];
edge[num].f=f;
pre[x]=num++;
}
bool Bellman()
{
memset(dis,0,sizeof(dis));
for(int i=1;i<=N;i++)
for(int j=0;j<num;j++)
{
int v=edge[j].v,u=edge[j].u,w=edge[j].f;
if(dis[u]>dis[v]+w)
{
dis[u]=dis[v]+w;
if(i>=N)
return true;
}
}
return false;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
while(cin>>N,N)
{
int s,n,k;
char o[5];
init();
scanf("%d",&M);
for(int i=0;i<M;i++)
{
scanf("%d%d%s%d",&s,&n,o,&k);
if(o[0]=='g')
add_edge(s+n,s-1,-1-k);
else add_edge(s-1,s+n,k-1);
}
for(int i=0;i<=N;i++)
add_edge(N+1,i,0);
if(Bellman())
printf("successful conspiracy\n");
else printf("lamentable kingdom\n");
}
return 0;
}