差分約束系統


 

一、算法簡介

  差分約束系統是一種特殊的N元一次不等式組,它包含N個變量,X1~Xn以及m個約束條件,每一個約束條件都是由兩個變量作差構成的,形如Xi-Xj≤Ck,其中Ck是常數,1≤i,j≤N,1≤k≤M,解決的問題是求一組解,X1=a1,X2=a2~~~Xn=an,使得所有的約束條件都得到滿足。

  差分約束系統的每一個約束條件Xi-Xj≤Ck可以變形為最短路中的三角不等式,Xi≤Xj+Ck,因此,可以把每一個變量Xi看作有向圖中的一個結點i,對於每一個約束條件Xi-Xj≤Ck,為從結點j想結點i連一條長度為Ck的有向邊

二、例題

  UVA1723 Intervals

  此題是差分約束的裸題,但我之前也不知道差分約束系統是什么。

  定義:差分約束系統是一種特殊的N元一次不等式,包含N個變量及M個約束條件,每個約束條件由兩個變量作差構成,形如Xi-Xj>=Ck,Ck是常數,1<=i,j<=N,1<=k<=M。如

  X1-X0>=2   X2-X1>=3   X3-X2>=2   X3-X0>=6

  通過消元可得到X3-X0的最小值為7。假若我們從0->1建一條權值為2的邊,從1->2建一條權值為3的邊,以此類推。從0到3跑一遍SPFA求最長路,發現也為7。

  這不是個巧合,因為求最長路時,SPFA的松弛操作后,dis[v]>=dis[u]+g[i].w,與不等式Xi>=Xj+Ck性質一樣,所以SPFA可以求出最小值,又因為不等式同大取大,所以要求出最長路。所以SPFA死了嗎?

  最后,一定要注意輸出,每個答案空一行,最后一個答案最后要輸出換行。

  本題就是從0-50000選出最少的數,使每個區間至少有c個數被選,這是求最小值,所以我們待會要將所有的不等式轉換成">="的形式 我們用數組d[i]表示0-i之間至少要選多少個數,所以對於每一個約束條件,可以看成d[b_i]-d[a_i-1]>=c_id[bi]d[ai1]>=ci

  d[bi]>=ci+d[ai1]

  因此我們在a[i-1]和b[i]之間建一條長度為c[i]的邊

  上述建邊過程只是題干中給出的條件,我們還要找出題目中的隱藏條件(隱藏條件視題目而為,有則加)。

  因為d[i]表示的是0-i之間要選的最小個數,所以d[i]一定要大於d[i-1];又因為每個數只能選一次,所以d[i]-d[i-1]要么為0,要么為1;

  由上述兩個條件,我們對應出兩個不等式d_i-d_{i-1}>=0didi1>=0d_{i-1}-d_{i}>=-1di1di>=1

  所以我們在i-1和i之間連長度為0的有向邊,在i和i-1之間連長度為-1的有向邊

  因為i的范圍是0-50000所以i-1會變成負數,我們都知道,若數組的下標為負,程序會運行錯誤,所以我們不妨給所有的變量加1,這樣就不會出現負下標了,所以上述加邊過程可簡述為

    1---->在a[i]和b[i]+1之間連一條長度為c[i]的有向邊     2---->在i和i+1之間連一條長度為0的有向邊     3---->在i+1和i之間連一條長度為-1的有向邊

  接着,設d[0]為1,以0為起點求單元最長路

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
int t,n,head[500005],tot,ma,dis[500005];
bool vis[500005];
struct node{
    int u,v,next,w;
}g[5000005];
inline int rea(){//快讀
    int s=0,g=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')
            g=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=(s<<3)+(s<<1)+(ch^48);
        ch=getchar();
    }
    return s*g;
}
inline void add(int u,int v,int w){//建圖
    g[++tot].u=u;
    g[tot].v=v;
    g[tot].w=w;
    g[tot].next=head[u];
    head[u]=tot;
}
inline void spfa(){//SPFA求最長路,即可得出最小值
    queue<int>q;
    q.push(0);
    vis[0]=true;
    dis[0]=0;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=false;
        for(int i=head[u];i;i=g[i].next){
            int v=g[i].v;
            if(dis[v]<dis[u]+g[i].w){
                dis[v]=dis[u]+g[i].w;
                if(!vis[v]){
                    vis[v]=true;
                    q.push(v);
                }
            }
        }
    }
}
int main(){
    t=rea();
    while(t--){
        memset(head,0,sizeof(head));
        memset(dis,-0x3f,sizeof(dis));
        tot=0;
        n=rea();
        for(int i=1;i<=n;i=-~i){
            int a=rea(),b=rea(),c=rea();
            add(a,b+1,c);//因為前綴和中,Sb-S(a-1)>=c,然而a-1要大於等於0,a有可能是0,所以我們加一
            ma=max(ma,b+1);
        }
        for(int i=1;i<=ma;i=-~i){
            add(i-1,i,0);
            add(i,i-1,-1);//Si-S(i-1)>=0,S(i-1)-Si>=-1
        }
        spfa();
        printf("%d\n",dis[ma]);
        if(t)
            printf("\n");//注意輸出!!!
    }
    return 0;
}

 

 

三、相關文章鏈接

  https://blog.csdn.net/dragon60066/article/details/80245797

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM