再次强调,转载请注明出处!!!
http://blog.csdn.net/abc13068938939/article/details/52141616
差分约束系统
差分约束系统是线性规划问题的一种。在一个差分约束系统中,线性规划矩阵A的每一行包含一个1和一个-1,A的所有其他元素都为0。因此AX<=B给出的差分约束条件是m个差分约束集合,其中包括n个未知元。每个约束条件为如下形式的简单线性不等式:xj-xi<=bk,其中1<=i,j,<=n;1<=k<=m。
通过观察不难发现:每个约束条件的不等式与求单源最短路径算法中的松弛操作极为类似。将图形理论与差分约束系统AX<=B加以联系:m*n的线性规划矩阵A可被看作n个顶点,m条边的图的关联矩阵的转置。对于i属于[1,n],图中每个顶点对应着n个未知量的一个xi,每条有向边对应着两个未知量的m个不等式的其中一个。
这样通过求解建图的单源最短路径问题就能得到差分约束系统的一组解。
为保证图的连通性,有时会引入附加节点vs使图中每个顶点vi都能从vs可达,并设弧< vs,vi>的权w(vs,vi)=0。对于每个差分约束xj-xi<=bk(助于是小于等于号),则弧< vi,vj>的权w(vi,vj)=bk。此时初始化dist[vs]=0;
dist[vi]=INF。
接下来就是求解以vs为源点的单源最短路径。由于差分约束系统一般都存在负值,所以一般用SPFA算法。
注意:如果图中存在负权回路,则该差分约束系统不存在可行解。如果vs到某点不存在最短路径,即最短路径为INF,则该点表示的变量可以取任意值,都能满足差分约束系统的条件。
文字不好理解,举个两个例子
已知B-A<=DD1(1式),D-B<=DD2(2式),C-A<=DD3(3式),D-C<=DD4(4式),求D-A的最大值。
(1)+(2),得:D-A<=DD1+DD2;
(3)+(4),得:D-A<=DD3+DD4;
显然就是求min(DD1+DD2,DD3+DD4)
转化为图,源点是A,dist(i,j)表示j到i的距离(有方向,i->j),
dist(A,B)=DD1,dist(B,D)=DD2,dist(A,C)=DD3,dist(C,D)=DD4.
求D到A的最短路dist(A,D)。
已知B-A<=D1,C-A>=D0,C-B<=D2。
首先先都变为<=,
B-A<=D1(1式),A-C<=-D0(2式),C-B<=D2(3式)。
(1)+(3),得:C-A<=D2+D1(4式);
我们可以看成是否有解就是
(2)+(4),0+0<=D2+D1-D0;
换句话说也就是是否存在负环。
再转化为图,源点是A,dist(i,j)表示j到i的距离(有方向,i->j),
dist(A,B)=D1,dist(C,A)=-D0,dist(B,C)=D2.
显然这是一个环,它有没有解就看是否存在负环。
例题POJ3159Candies
Candies
题意:总共n个点,m对 A B c 表示B-A<=c
要求1和n的差值(n-1)最大
每个约束B-A<=c 就是B<=A+c 加边A->B 为c的边。
建图以后就是求最短路。
由于这道题都是正值,所以spfa可以,dijkstra也可以。但是spfa+queue超时,spfa+dfs超时,要用spfa+stack。dijkstra要用堆优化,以下为dijkstra+堆的代码。并且这道题还卡stl
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <stack>
#include <vector>
#include <string.h>
#include <queue>
#define msc(X) memset(X,-1,sizeof(X))
#define ms(X) memset(X,0,sizeof(X))
typedef long long LL;
using namespace std;
const int MAXN=30010;
const int MAXM=150010;
const int INF=0x3f3f3f3f;
struct _Edge
{
int to,next,w;
bool operator < (const _Edge &a) const{
return w>a.w;
}
}edge[MAXM];
int hd[MAXM],d[MAXM],tot;
bool vs[MAXM];
void addedge(int u,int v,int w)
{
edge[tot].to=v;
edge[tot].w=w;
edge[tot].next=hd[u];
hd[u]=tot++;
}
void Dijkst(int beg,int n)
{
struct _Edge tmp;
ms(vs);
for(int i=1;i<=n;i++) d[i]=INF;
d[beg]=0;
tmp.to=beg,tmp.w=0;
priority_queue<struct _Edge> q;
while(!q.empty()) q.pop();
q.push(tmp);
while(!q.empty()){
tmp=q.top();
q.pop();
int u=tmp.to;
if(vs[u]) continue;
vs[u]=true;
for(int i=hd[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(!vs[v]&&d[v]>d[u]+edge[i].w){
tmp.w=d[v]=d[u]+edge[i].w;
tmp.to=v;
q.push(tmp);
}
}
}
}
int main(int argc, char const *argv[])
{
int n,m;
scanf("%d %d",&n,&m);
msc(hd);
tot=0;
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
addedge(a,b,c);
}
Dijkst(1,n);
printf("%d\n",d[n] );
return 0;
}
例题poj3169Layout
Layout
对于ml中的a b d
有b-a<=d(建a->b 的边,权值为d)
对于md中的a b d
有b-a>=d即a-b<=-d(建a->b 的边,权值为-d)
求以1为源点的最短路,有负值,所以不能用dijkstra。
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <stack>
#include <vector>
#include <string.h>
#include <queue>
#define msc(X) memset(X,-1,sizeof(X))
#define ms(X) memset(X,0,sizeof(X))
typedef long long LL;
using namespace std;
const int MAXN= 1010;
const int MAXM= 20010;
const int INF= 0x3f3f3f3f;
struct _Edge
{
int to,c,next;
}edge[MAXM];
int tot,hd[MAXN],d[MAXN],cnt[MAXN];
bool vs[MAXN];
void addedge(int u,int v,int c)
{
edge[tot].to=v;
edge[tot].c=c;
edge[tot].next=hd[u];
hd[u]=tot++;
}
bool SPFA(int beg,int n)
{
ms(vs);
for(int i=1;i<=n;i++) d[i]=INF;
d[beg]=0;
queue<int> q;
while(!q.empty()) q.pop();
ms(cnt);
cnt[beg]=1;
vs[beg]=true;
q.push(beg);
while(!q.empty()){
int u=q.front();
q.pop();
vs[u]=false;
for(int i=hd[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(d[v]>d[u]+edge[i].c)
{
d[v]=d[u]+edge[i].c;
if(!vs[v]){
if(++cnt[v]>n) return false;
vs[v]=true;
q.push(v);
}
}
}
}
return true;
}
int main(int argc, char const *argv[])
{
int n,ml,md;
scanf("%d %d %d",&n,&ml,&md);
msc(hd);
while(ml--){
int a,b,d;
scanf("%d %d %d",&a,&b,&d);
addedge(a,b,d);
}
while(md--){
int a,b,d;
scanf("%d %d %d",&a,&b,&d);
addedge(b,a,-d);
}
if(!SPFA(1,n)) puts("-1");
else if(d[n]==INF) puts("-2");
else printf("%d\n",d[n] );
return 0;
}
例题poj1201Intervals
Intervals
本题的意思是要构造一个集合,这个集合内的数字满足所给的n个条件,每个条件都是在[a,b]内至少有c个数在集合内。问这个集合最少包含几个点。
这道题是一道比较好的差分约束系统的例题,转化的方法值得学习。转化的目的是要达到tb-ta<=k的形式,那么就要给ti一个合理的意义才行。对于这道题,由于输入数据没有负数,但是有0,就可以选[0,i-1]的范围内,要选多少个数。那么对于题中所说的每个条件[a,b]内至少有c个数在集合中就可以表示为t(b+1)-ta>=c,即ta-t(b+1)<=-c。并且数列ti是递增的,增量最大为1,即0<=ti-t(i-1)<=1,这个式子等价于t(i-1)-ti<=0,ti-t(i-1)<=1.将上面的所有条件组合起来就是这道题的差分约束条件。
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <stack>
#include <vector>
#include <string.h>
#include <queue>
#define msc(X) memset(X,-1,sizeof(X))
#define ms(X) memset(X,0,sizeof(X))
typedef long long LL;
using namespace std;
const int MAXN=50050;
const int INF=0x3f3f3f3f;
struct _Edge
{
int e,c,next;
}edge[3*MAXN];
int d[MAXN],hd[MAXN],tot;//d[i]代表0~i-1中要选几个数
bool vs[MAXN];
void addedge(int u,int v,int c)
{
edge[tot].e=v;
edge[tot].c=c;
edge[tot].next=hd[u];
hd[u]=tot++;
}
void SPFA(int s,int e)
{
ms(vs);
for(int i=s;i<=e;i++) d[i]=INF;
d[s]=0;
vs[s]=true;
queue<int > q;
while(!q.empty()) q.pop();
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vs[u]=false;
for(int i=hd[u];i!=-1;i=edge[i].next)
{
int v=edge[i].e;
if(d[v]>d[u]+edge[i].c)
{
d[v]=d[u]+edge[i].c;
if(!vs[v]){
vs[v]=true;
q.push(v);
}
}
}
}
}
int main(int argc, char const *argv[])
{
int n,a,b,c,beg=INF,ed=0;;
scanf("%d",&n);
tot=0;
msc(hd);
for(int i=0;i<n;i++)
{
scanf("%d %d %d",&a,&b,&c);
b++;
addedge(a,b,-c);
beg=min(a,beg);
ed=max(ed,b);
}
for(int i=0;i<ed;i++)
{
addedge(i,i+1,0);
addedge(i+1,i,1);
}
SPFA(beg,ed);
printf("%d\n",-d[ed] );
return 0;
}