差分约束系统

         首先说说定义,差分约束系统是指一组不等式,且每个不等式都由两个变量和一个常数组成,形如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;
    }
}
























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值