首先说说定义,差分约束系统是指一组不等式,且每个不等式都由两个变量和一个常数组成,形如v<=u+w或v>=u+w,u和v是变量,w是常数。每个不等式都是一个约束条件,约束目的是使目标函数xs-xt最大或最小。
差分约束系统属于线性规划问题,不过神奇的是可以转换为图论问题,先做些简单的说明,就像有一组不等式:
b-a<=k1,
c-b<=k2,
c-a<=k3,
上述不等式组就是一个差分约束系统,每个不等式都是两个变量之差小于(当然也可以大于)等于某个常数。现在要求c-a的最大值,明显,这里可以用线性规划解决,但我们发现,上述每个不等式都与求解最短路径问题时的三角不等式相像,即边u->v 的权值为w的话,有d[v]<=d[u]+w,变换下就有d[v]-d[u]<=w,因此,我们可以将a,b,c三个变量看做三个节点,k1,k2,k3看做三个边的权值,于是可以构建出下图:
求c-a的最大值也就是求从a到c的最短路,事实上,由b-a<=k1和c-b<=k2可得c-a<=k1+k2,则k1+k2和k3的最小值就是c-a的最大值,从这里我们也可以从直观上理解为什么从a到c的最短路就是c-a的最大值。
下面再以另一个例子做进一步介绍。
X1 - X2 <= 0
X1 - X5 <= -1
X2 - X5 <= 1
X3 - X1 <= 5
X4 - X1 <= 4
X4 - X3 <= -1
X5 - X3 <= -3
X5 - X4 <= -3
这也是一个差分约束系统,总共有5个变量,8个约束条件。要求满足所有不等式的一组解。
由前一个例子知道,差分约束系统的解法就是利用了解单源最短路径时的三角不等式d[v]-d[u]<=w,对这类问题,在找出所有不等式后,就是要建图。5个变量就是5个节点,8个约束条件就是8条边,每个约束条件xi-xj<=k代表一条有向边<xj,xi>的权值为k(如果是xi-xj>=k,变为xj-xi<=-k),求出新建图的单源最短路径就是一组解。
另外,差分约束系统要么无解,要么有无数组解,因为当在所有不等式两边同时加上某个数时,就可以得到另外一组解。比如在本例中,{-5,-3,0,-1,-4}是一组解,加5后,{0,2,5,4,1}是另一组解。
当然,既然是求单源最短路径,自然要有一个源点,而这个源点需要我们自己加上去,否则图可能不连通。通常这个源点与其它节点的距离设为0(当然也可以设为任何比INF小的值,加入源点只是为了要使图保持连通性,同时也更好理解一些)。假设加入的源点为x0,从不等式的角度出发,也可以理解为xi-x0<=0,i=1,2,3,4,5。于是本例构建的图如下:
接下来只需要求解从源点v0出发,到达其它各点的最短路就行了。
另外需要注意的几点:
1:要求目标函数vt-vs的最大值,需要把所有的不等式化为vi-vj<=k的形式:,建图后求最短路。求最小值的话就化为vi-vj>=k的形式,建图后求最长路。
2:若图中存在负权回路,这差分约束系统无解。
3:若只需要判断差分约束系统是否有解,那么可以不用加入源点,用bellman-ford算法判断是否有负权回路,但要注意的是这时不能用spfa算法,因为前者不管图是否连通,都会对所有边进行松弛操作,而后者就不行了。
下面通过poj上的一些题来作为练习。
poj 3159
很典型的差分约束系统的题。输入表示有n个点,m个约束条件,每个约束条件给出为 A B c,表示d[B]-d[A]<=c,要求d[n]-d[1]的最大值。直接建图求最短路就行了。
代码如下:
#include <cstdio>
#include <stack>
#include <set>
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <functional>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <string>
#include <map>
#include <iomanip>
#include <cmath>
#define LL long long
#define ULL unsigned long long
#define SZ(x) (int)x.size()
#define Lowbit(x) ((x) & (-x))
#define MP(a, b) make_pair(a, b)
#define MS(arr, num) memset(arr, num, sizeof(arr))
#define PB push_back
#define F first
#define S second
#define ROP freopen("input.txt", "r", stdin);
#define MID(a, b) (a + ((b - a) >> 1))
#define LC rt << 1, l, mid
#define RC rt << 1|1, mid + 1, r
#define LRT rt << 1
#define RRT rt << 1|1
#define BitCount(x) __builtin_popcount(x)
#define BitCountll(x) __builtin_popcountll(x)
#define LeftPos(x) 32 - __builtin_clz(x) - 1
#define LeftPosll(x) 64 - __builtin_clzll(x) - 1
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
using namespace std;
const double eps = 1e-8;
const int MAXN = 300 + 10;
const int MOD = 1000007;
const int M=20010;
const int N=300010;
const int dir[][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };
typedef pair<int, int> pii;
int n,m,r,d[N];
int w[N],u[N],v[N],first[N],next[N];
bool vis[N];
void build()
{
int i,j;
MS(first,-1);
for (i=0;i<m;i++) {
scanf("%d%d%d",u+i,v+i,w+i);
next[i]=first[u[i]];
first[u[i]]=i;
}
}
int dijkstra()
{
int i,j;
priority_queue<pii,vector<pii>,greater<pii> > q;
for (i=1;i<=n;d[i++]=INF);
d[1]=0;
q.push(MP(d[1],1));
while(!q.empty()) {
int t=INF,pos;
//for (j=1;j<=n;j++) if (!vis[j] && t>d[j]) t=d[pos=j];
pii temp=q.top(); q.pop();
pos=temp.S;
if (d[pos]<temp.F) continue;
vis[pos]=true;
for (j=first[pos];j!=-1;j=next[j]) if (d[v[j]]>d[pos]+w[j]) {
d[v[j]]=d[pos]+w[j];
q.push(MP(d[v[j]],v[j]));
}
}
return d[n];
}
int main()
{
int i,j;
while(~scanf("%d%d",&n,&m))
{
build();
MS(vis,false);
cout<<dijkstra()<<endl;
}
}
poj 1364
这道题表示读题读到吐血。。。弄了老半天都不知道它要干嘛,简直气的牙花子都撮出来了。
题意简单的说就是给出n和m,n表示一个数字序列的长度,m表示有多少个约束条件。每个约束条件形如si ni oi ki,当oi为gt时,表示a[si]+a[si+1]+....+a[si+ni]>ki,当oi为lt时,表示a[si]+a[si+1]+....+a[si+ni]<ki。求是否存在一个长度为n的数字序列能满足所有约束条件。
首先把子序列的和用前n项和之差来表示,并且将>转换为>=,将<转换为<=,即si ni gt ki转换为sum[si+ni] - sum[si-1] >= ki-1,si ni lt ki转换为sum[si+ni] - sum[si-1] <= ki-1,然后建图,判断是否有负权回路。若有,则不存在,否则存在。
代码如下:
#include <cstdio>
#include <stack>
#include <set>
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <functional>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <string>
#include <map>
#include <iomanip>
#include <cmath>
#define LL long long
#define ULL unsigned long long
#define SZ(x) (int)x.size()
#define Lowbit(x) ((x) & (-x))
#define MP(a, b) make_pair(a, b)
#define MS(arr, num) memset(arr, num, sizeof(arr))
#define PB push_back
#define F first
#define S second
#define ROP freopen("input.txt", "r", stdin);
#define MID(a, b) (a + ((b - a) >> 1))
#define LC rt << 1, l, mid
#define RC rt << 1|1, mid + 1, r
#define LRT rt << 1
#define RRT rt << 1|1
#define BitCount(x) __builtin_popcount(x)
#define BitCountll(x) __builtin_popcountll(x)
#define LeftPos(x) 32 - __builtin_clz(x) - 1
#define LeftPosll(x) 64 - __builtin_clzll(x) - 1
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
using namespace std;
const double eps = 1e-8;
const int MAXN = 300 + 10;
const int MOD = 1000007;
const int M=20010;
const int N=300010;
const int dir[][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };
typedef pair<int, int> pii;
int n,m,si,ni,ki,d[N];
char s[N];
int w[N],u[N],v[N],first[N],next[N],cnt[N];
bool vis[N];
void add(int a,int b,int c,int i)
{
u[i]=a; v[i]=b; w[i]=c;
next[i]=first[a];
first[a]=i;
}
void build()
{
int i,j;
MS(first,-1);
for (i=0;i<m;i++){
scanf("%d %d %s %d",&si,&ni,s,&ki);
int a=si+ni,b=si-1;
if (s[0]=='g') add(a,b,-ki-1,i);
else add(b,a,ki-1,i);
}
for (j=0;j<=n;j++,i++) add(n+1,j,0,i); // 将n+1作为源点加入图中,这里0不能作为源点,因为图中已经包括了0这个节点
}
bool spfa()
{
int i,j;
queue<int> q;
q.push(n+1);
MS(vis,false);
MS(cnt,0);
for (i=0;i<=n;i++) d[i]=INF;
d[n+1]=0; cnt[n+1]++;
while(!q.empty())
{
int pos=q.front(); q.pop();
vis[pos]=false;
for (i=first[pos];i!=-1;i=next[i]){
if (d[v[i]]>d[u[i]]+w[i]){
d[v[i]]=d[u[i]]+w[i];
if (!vis[v[i]]){
vis[v[i]]=true;
if (++cnt[v[i]]>n+1) return false;
q.push(v[i]);
}
}
}
}
return true;
}
int main()
{
int i,j;
while(~scanf("%d",&n),n)
{
scanf("%d",&m);
build();
if (spfa()) puts("lamentable kingdom"); // 存在
else puts("successful conspiracy"); // 不存在
}
}
poj 3169
题意:有n个奶牛,编号从1到n,按顺序站为一排等待进食。在排队过程中,有好感的两个奶牛之间距离不能超过给定值,没有好感的两个奶牛之间距离不能小于某个值,求1号奶牛与n号奶牛之间的最大距离。
输入给出n,ml,md,表示有n个奶牛,其中ml对奶牛相互有好感,md对奶牛相互没有好感。接着ml行,每行表示哪两个奶牛相互有好感以及给定距离值,再是md行,表示哪两个奶牛相互没有好感以及给定距离值。
设d[i]表示i号奶牛距离1号奶牛的距离,于是对有好感的奶牛有d[j]-d[i]<=k(i<j,k是给定距离值),没有好感的奶牛有d[j]-d[i]>=k,由于是求最大值,将d[j]-d[i]>=k转换为d[i]-d[j]<=-k(表示有向弧<j,i>的权值为-k),这样就有了ml+md个形式一样的约束条件。
不过需要注意的是题目中还有一个隐含的约束条件:d[i]<=d[j],即d[i]-d[j]<=0。于是约束条件总共有ml+md+n-1个。
找出所有约束条件后,剩下的就是建图求最短路了。
代码如下:
#include <cstdio>
#include <stack>
#include <set>
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <functional>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <string>
#include <map>
#include <iomanip>
#include <cmath>
#define LL long long
#define ULL unsigned long long
#define SZ(x) (int)x.size()
#define Lowbit(x) ((x) & (-x))
#define MP(a, b) make_pair(a, b)
#define MS(arr, num) memset(arr, num, sizeof(arr))
#define PB push_back
#define F first
#define S second
#define ROP freopen("input.txt", "r", stdin);
#define MID(a, b) (a + ((b - a) >> 1))
#define LC rt << 1, l, mid
#define RC rt << 1|1, mid + 1, r
#define LRT rt << 1
#define RRT rt << 1|1
#define BitCount(x) __builtin_popcount(x)
#define BitCountll(x) __builtin_popcountll(x)
#define LeftPos(x) 32 - __builtin_clz(x) - 1
#define LeftPosll(x) 64 - __builtin_clzll(x) - 1
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
using namespace std;
const double eps = 1e-8;
const int MAXN = 300 + 10;
const int M=20010;
const int N=50010;
const int dir[][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };
typedef pair<int, int> pii;
int n,ml,md,d[N];
struct edge
{
int u,v,w;
}e[N];
int bellman()
{
int i,j;
for (i=1;i<=n;i++) d[i]=INF;
d[1]=0;
for (i=0;i<n;i++){
for (j=0;j<ml+md+n-1;j++){
if (d[e[j].u]!=INF && d[e[j].v]>d[e[j].u]+e[j].w) {
d[e[j].v]=d[e[j].u]+e[j].w;
//if (i==n-1) return -1;
}
}
}
if (d[1]<0) return -1;
else if (d[n]==INF) return -2;
else return d[n];
}
int main()
{
int i,j;
while(~scanf("%d%d%d",&n,&ml,&md))
{
for (i=0;i<ml;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
for (j=i;j<i+md;j++) { scanf("%d%d%d",&e[j].v,&e[j].u,&e[j].w); e[j].w=-e[j].w; }
for (i=1;i<=n-1;i++) {
e[j].u=i+1;
e[j].v=i;
e[j].w=0;
j++;
}
int t=bellman();
cout<<t<<endl;
}
}