差分约束系统 poj

注意:差分约束系统

注意:差分约束系统;
SPFA 分为用队列,用栈和用优先队列的问题。
因为数据的问题有时用队列会超时,可以用栈和优先队列;有时栈会超时,可以用优先队列和队列;

先建出满足所有条件的不等式组,根据建图的方向不同初始化问题也会不同。
首先必须方向统一,即大于号或小于号必须统一
超级源点的构建就是为了让所有的点都可以进入队列,保证所有的点都联通,并且超级源点不可以去其他点相重复。
如果用贝尔曼 福特 就不存在超级源点

 如果一个系统由n个变量和m个不等式组成,形如 Xj-Xi<=b*k(i,j属于[1,n],k属于[1,m] ), 这样的系统称之为差分约束系统。差分约束系统通常用于求关于一组变量的不等式组。

   求解差分约束系统可以转化为图论中单源最短路问题。

  Xj-Xi<=k  ==>   d[v]-d[u]<=w[u,v]   ( 这里是我们要根据约束条件求的结果,尽量往它靠近) ==>   所以转化为图论求解 ,也就是if(d[v]-d[u] >w[u,v]) 那么  d[v]-d[u]<=w[u,v] 。  路径距离初始化 dis[i]=INF

  Xj-Xi>=k  ==>   d[v]-d[u]>=w[u,v]   ( 这里是我们要根据约束条件求的结果,尽量往它靠近) ==>   所以转化为图论求解 ,也就是if(d[v]-d[u] <w[u,v]) 那么  d[v]-d[u]>=w[u,v]  。  

路径距离初始化 dis[i]=-INF

  再增加一个源点s,源点到所有定点的距离为0( 添加源点的实质意义为默认另外一系列的不等式组Xi-Xo<=0),再对源点利用spfa算法。

 

  注意几个问题:

  1、当0没有被利用的时候,0作为超级源点。当0已经被利用了,将n+1(未被利用)置为超级源点。

  2、对于Xj-Xi=k 可以转换为 Xj-Xi<=k Xj-Xi>=k来处理。

  3、a.若要判断图中是否出现负环,可以利用深度优先搜索。以前利用spfa是这样的(head->****->tail),当head和tail之间所有点都遍历完了才轮得上tail这个点,这样的话我们无法判断图中有没有负环,我们可以这样改变一样遍历顺序,head->tail->***->head。 当深度优先搜索过程中下次再次遇见head(也就是head节点依然在标记栈中)时,则可判断图中含有负环,否则没有。

         b.也可以判断某个点被访问超过n次就是存在负权环了

 注意负值的取法。

  4、当图连通时,只需要对源点spfa一次;当图不连通时,对每个定点spfa一次。

  5、对于  Xj-Xi<k or  Xj-Xi>k  , 差分约束系统只针对>= or <=, 所以我们还要进行巧妙转换编程大于等于小于等于。

  6. 对于A-x==B 问题需要转换为 A—B>=x &&A-B<=x 也就是Build(A,B,-x),Build(B,A,x)建立两次



POJ 2983

题意:给出两种关于防御站位置的信息,一种是确切的信息,P A B X,表示a在b北面x距离的地方,另一种是V A B,表示只知道A在B的北面。问这些信息有没有矛盾。

分析:差分约束。第一种记为A-B>=X&&A-B<=X,第二种记为A-B>=0,用SPFA求得解,则没有矛盾,否则就有矛盾。

方程一: //倒着建图(代码正好与正向相反)

不等式方程 dis[a]>=dis[b]+x 是正常条件  

此时需要将dis[i]为-INF;

当dis[a]<dis[b]+x 是需要更新dis[a]的。

Build(A,B,x),Build(B,A,-x);


方程二  //正着建图

 dis[a]-x>=dis[b]

dis[i]初始化为INF;

当dis[b]>dis[a]-x时 需要更新dis[b];

Build(A,B,-x),Build(B,A,x);


对于相等的条件是 A-B>=X&&A-B<=X 也就是将相等的条件建成

Build(A,B,-x),Build(B,A,x);

看是否有负权环,形成矛盾。

用栈超时,用队列或优先队列可过

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <algorithm>
#include <cmath>
#include <ctime>
#define LL long long
#define Pr pair<int,int>
#define bug puts("*********")
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)

using namespace std;

const int INF = 0x3f3f3f3f;
const double eps = 1e-8;
const int mod = 1e9+7;
const int N = 212345;
int num=0;
int sta[N];
int head[N];
int dis[N];
int vis[N];
int used[N];
int n,m;
struct node{
    int to;
    int val;
    int next;
}p[N];

void Build(int x,int y,int val){
    p[num].to=y;
    p[num].val=val;
    p[num].next=head[x];
    head[x]=num++;
}
void SPFA(){
    memset(dis,INF,sizeof(dis));
    memset(vis,0,sizeof(vis));
    memset(used,0,sizeof(used));
    //queue<int>Q;
    priority_queue<int>Q;
    int top=0;
    //sta[++top]=0;
    Q.push(0);
    vis[0]=1;
    dis[0]=0;
    while(!Q.empty()){
      //  int x=sta[top--];
        //vis[x]=0;
        int x=Q.top();
        Q.pop();
        vis[x]=0;
        for(int i=head[x];i!=-1;i=p[i].next){
            int to=p[i].to;
            if(dis[to]>dis[x]+p[i].val){
                dis[to]=dis[x]+p[i].val;
                if(!vis[to]){
                    vis[to]=1;
                    used[to]++;
                    //sta[++top]=to;
                    Q.push(to);
                    if(used[to]>n){
                        puts("Unreliable");
                        return;
                    }
                }
            }
        }
    }
    puts("Reliable");
}
char s[10];
int main(){
    int u,v,z;
    while(~scanf("%d%d",&n,&m)){
        memset(head,-1,sizeof(head));
        num=0;
        for(int i=1;i<=m;i++){
           scanf("%s",s);
           if(s[0]=='P'){
            scanf("%d%d%d",&u,&v,&z);
            Build(v,u,z);
            Build(u,v,-1*z);
           }
           else{
            scanf("%d%d",&u,&v);
            Build(u,v,-1);
           }
        }
        for(int i=1;i<=n;i++){
            Build(0,i,0);
        }
        SPFA();
    }
    return 0;
}


求最小的解

POJ1201

题目说[ai, bi]区间内和点集Z至少有ci个共同元素,那也就是说如果我用Si表示区间[0,i]区间内至少有多少个元素的话,那么Sbi - Sai >= ci,这样我们就构造出来了一系列边,权值为ci,但是这远远不够,因为有很多点依然没有相连接起来(也就是从起点可能根本就还没有到终点的路线),此时,我们再看看Si的定义,也不难写出0<=Si - Si-1<=1的限制条件,虽然看上去是没有什么意义的条件,但是如果你也把它构造出一系列的边的话,这样从起点到终点的最短路也就顺理成章的出现了。

那么显然对于组合[a,b,c] 我们有 s[b] – s[a-1]  >= c (a-1是因为我们需要算上a)

还有:0<=  s[b]-s[b-1]  <=1

变形得

S[b]-S[b-1] >=0
S[b-1]-S[b] >=-1

接着,对于求最小值的来说,我们用最长路来求解。为什么是最长路?一开始把距离d设为无穷小,之后最长路的更新公式为:if(d[v] < d[u]+w(u,v)) d[v]=d[u]+w(u,v); 可以看到d[v]不断的增大,即当满足条件的时候为最小的解。

所以我们在建图的时候,需要满足最长路的三角不等式: d(v) >= d(u) + w(u, v) =>d(v) – d(u) >=w(u,v) 的形式=>从u到v连接一条权重为w(u,v)的边。

故本题中三个不等式都是如下的形式:

  • s[b] – s[a-1] >= c
  • S[b]-S[b-1] >=0
  • S[b-1]-S[b] >=-1

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define eps 0.00000001
#define LL long long
using namespace std;
const int MM=150010;
const int NN=50010;
struct node{
    int to,val,next;
}p[MM];
int num=0,n,m;
int head[NN];
int dis[NN];
int vis[NN];
int Max,Min;
void Build(int x,int y,int z){
    p[num].to=y;
    p[num].val=z;
    p[num].next=head[x];
    head[x]=num++;
}
int sta[MM];
void spfa(){
    //memset(sta,0,sizeof(sta));
    memset(dis,-INF,sizeof(dis));
    memset(vis,0,sizeof(vis));
   // cout<<Min<<endl;
    dis[Min]=0;
   // cout<<dis[2]<<endl;
    vis[Min]=1;
    int top=0;
    sta[++top]=Min;
    while(top){
        int x=sta[top];
        top--;
        vis[x]=0;
        for(int i=head[x];i!=-1;i=p[i].next){
            int to=p[i].to;
            if(dis[x]+p[i].val>dis[to]){
                dis[to]=dis[x]+p[i].val;
                if(!vis[to]){
                    vis[to]=1;
                    sta[++top]=to;
                }
            }
        }
    }
//cout<<Max<<endl;
    printf("%d\n",dis[Max]);
}
int main(){
    while(~scanf("%d",&n)){
        num=0;
        Max=0;
        Min=INF;
        memset(head,-1,sizeof(head));
        for(int i=0;i<n;i++){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            Max=max(Max,b+1);
            Min=min(Min,a);
            Build(a,b+1,c);
        }
        for(int i=Min;i<=Max;i++){
            Build(i,i+1,0);
            Build(i+1,i,-1);
        }
        spfa();
    }
    return 0;
}

 
 
 

POJ3159

班上有n个同学,现在有一些糖要分给他们,设第i个同学得到的糖为p[i],分糖必须满足条件:第i个同学要求第j个同学的糖不能超过自己k个,即p[j] - p[i] <= k,k >= 0。要求在满足这些条件的情况下,求出p[n] - p[1]的最大值。

不妨将糖果数当作距离,把相差的最大糖果数看成有向边AB的权值,我们得到 dis[B]-dis[A]<=w(A,B)。看到这里,我们联想到求最短路时的松弛技术,即if(dis[B]>dis[A]+w(A,B), dis[B]=dis[A]+w(A,B)。即是满足题中的条件dis[B]-dis[A]<=w(A,B),由于要使dis[B] 最大,所以这题可以转化为最短路来求。这题如果用SPFA 算法的话,则需要注意不能用spfa+queue 来求,会TLE ,而是用 spfa + stack

用优先 队列也行

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define eps 0.00000001
#define LL long long
using namespace std;
const int MM=150010;
const int NN=30010;
struct node{
    int to,val,next;
}p[MM];
int num=0,n,m;
int head[NN];
int dis[NN];
int vis[NN];
void Build(int x,int y,int z){
    p[num].to=y;
    p[num].val=z;
    p[num].next=head[x];
    head[x]=num++;
}
int sta[MM];
void spfa(){
    //memset(sta,0,sizeof(sta));
    memset(dis,INF,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[1]=0;
   // cout<<dis[2]<<endl;
    vis[1]=1;
    int top=0;
    sta[++top]=1;
    while(top){
        int x=sta[top];
        top--;
        vis[x]=0;
        for(int i=head[x];i!=-1;i=p[i].next){
            int to=p[i].to;
            if(dis[x]+p[i].val<dis[to]){
                dis[to]=dis[x]+p[i].val;
                if(!vis[to]){
                    vis[to]=1;
                    sta[++top]=to;
                }
            }
        }
    }
    printf("%d\n",dis[n]);
}
int main(){
    while(~scanf("%d%d",&n,&m)){
        num=0;
        memset(head,-1,sizeof(head));
        for(int i=0;i<m;i++){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            Build(a,b,c);
        }
        spfa();
    }
    return 0;
}

POJ 1364

题目大意;
        已知一个序列a[1], a[2], ......, a[n],给出它的若干子序列以及对该子序列的
        约束条件,例如a[si], a[si+1], a[si+2], ......, a[si+ni],且a[si]+a[si+1]
        +a[si+2]+......+a[si+ni] < or > ki。求是否存在满足以上m个要求的数列。是
        则输出“lamentable kingdom”,否则输出“successful conspiracy”。
    
解题思路:
        s[a] + s[a+1] + …… + s[b] < c 可以转化成前n项和sum[b] - sum[a - 1] < c,
        为了能用Bellman_Ford,即将< 转化成 <= ,sum[b] - sum[a - 1] <= c - 1。
        转化为最长路或者最短路来判断是否有解都可以。
解题感想;
        注意题目求约束的最大,或者最小时,就要判断此刻要用的是最短路,还是最长路。
        

        要构建超级源点。就是为了让所有的点都可以被录入的队列中去。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<queue>
#include<map>
#define bug puts("**********");
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
const int N=200;
char op[10];
int head[N*N];
int vis[N];
int used[N];
int dis[N],n;
struct node{
    int to;
    int val;
    int next;
}p[N*N];
int num=0;
void Build(int u,int v,int d){
    p[num].to=v;
    p[num].val=d;
    p[num].next=head[u];
    head[u]=num++;
}
queue<int>Q;
void spfa(){
    while(!Q.empty()) Q.pop();
    for(int i=1;i<=n+2;i++) dis[i]=INF;
    memset(vis,0,sizeof(vis));
    memset(used,0,sizeof(used));
    vis[0]=1;
    dis[0]=0;
    Q.push(0);
    used[0]++;
    while(!Q.empty()){
        int x=Q.front();
        Q.pop();
        vis[x]=0;
        for(int i=head[x];i!=-1;i=p[i].next){
            int to=p[i].to;
            if(dis[x]+p[i].val<dis[to]){
                dis[to]=dis[x]+p[i].val;

                if(!vis[to]){
                    vis[to]=1;
                    Q.push(to);
                    used[to]++;
                    if(used[to]>n+2){
                        puts("successful conspiracy");
                        return ;
                    }
                }
            }
        }
    }
    puts("lamentable kingdom");
}
int main(){
    int u,v,d,m;
    while(~scanf("%d",&n),n){
        memset(head,-1,sizeof(head));
        num=0;
        scanf("%d",&m);
        for(int i=0;i<m;i++){  
            scanf("%d%d%s%d",&u,&v,op,&d);
            if(op[0]=='g')
            Build(u+v+1,u,-d-1);    ///不能减1 是因为会与超级源点重复
            else
            Build(u,u+v+1,d-1);
        }
        for(int i=1;i<=n+1;i++){  ///超级源点的构建
            Build(0,i,0);
        }
        spfa();
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值