差分约束系统总结

求解差分约束系统,可以转化成图论的 单源最短路径(或最长路径)问题。
观察xj-xi<=bk,会发现它类似最短路中的三角不等式d[v]<=d[u]+w[u,v],即d[v]-d[u]<=w[u,v]。因此,以每个变量xi为结点,对于约束条件xj-xi<=bk,连接一条边(i,j),边权为bk。我们再增加一个源点s,s与所有定点相连,边权均为0。对这个图,以s为源点运行Bellman-ford算法(或SPFA算法),最终{d[ i]}即为一组可行解。
关键点: 搞清楚最后求的是什么

给定若干个连续的整数区间[ai, bi]以及ci,现在要求这样一个集合Z,集合Z中的元素与每个给定的区间的元素交集至少有ci个。(zoj)

S[i] = sigma(tj)  1<= j <= i  前缀和。 ti =1 ,表示i这个数出现了。
那么显然有 
1)  S[b] - S[a-1] >= c  =>  S[a-1] - S[b] <= -c   , 边(b , a-1  , -c)
2)  0 <= S[i] - S[i-1] <= 1 
 =>  S[i]- S[i-1] <= 1  边 (i-1, i , 1) 
 => S[i-1] - S[i] <= 0  边(i , i-1 , 0) 
最关键的时候来了, 求什么?
区间[mi , mx] (出现数字的上下界)  S[mx] - S[mi-1] >= M  ,即求M。
=>  S[mi-1] - S[mx] <= -M   边(mx , mi-1 , -M) 。 这个时候源点已经赤裸裸的出来了,mx 。
const  int  maxn = 50008 ;
struct Edge{
       int v ;
       int w ;
       int next ;
}e[maxn<<2] ;

int    g[maxn]  , id ;
inline void add(int u , int v , int w){
       e[id].v = v ;
       e[id].w = w ;
       e[id].next = g[u] ;
       g[u] = id++ ;
}

queue<int> Q ;
bool  in[maxn] ;
int   dist[maxn] ;
void  spfa(int s){
      while(! Q.empty()) Q.pop() ;
      memset(in , 0 , sizeof(in)) ;
      in[s] = 1 ;
      memset(dist , 63 , sizeof(dist)) ;
      dist[s] = 0 ;
      Q.push(s) ;
      while(! Q.empty()){
           int u = Q.front()  ; Q.pop() ; 
           in[u] = 0 ;
           for(int i = g[u] ; i != -1 ; i = e[i].next){
                 int v = e[i].v  ; int w = e[i].w  ;
                 if(dist[u] + w < dist[v]){
                      dist[v] = dist[u] + w  ;
                      if(! in[v]){
                            in[v] = 1 ;
                            Q.push(v) ;
                      }
                 }
           }
      }
}

int main(){
    int n , i , mi , mx , a , b , c ;
    while(cin>>n){
         id = 0 ;
         memset(g , -1 , sizeof(g)) ;
         mi = maxn + 10 ;
         mx = -1 ;
         for(i = 1 ; i <= n ; i++){
              scanf("%d%d%d" ,&a ,&b ,&c) ;
              a++ ; b++ ;
              add(b , a-1 , -c) ;
              mi = min(a , mi) ;
              mx = max(b , mx) ;
         }
         for(i = mi ; i <= mx ; i++){
              add(i-1 , i , 1) ;
              add(i , i-1 , 0) ;
         }
         spfa(mx) ; ;
         printf("%d\n" , -dist[mi - 1]) ;
    }
    return  0 ;
}

给你一个N*M的矩阵C,问是否存在一个长度为N的数列a和长度为M的数列b使得所有C(i,j)*a(i)/b(j)在L , U范围内。

是否存在ai,bj,使得l<=cij*(ai/bj)<=u (1<=i<=n,1<=j<=m)成立(hdu)

把cij除到两边:l'<=ai/bj<=u',如果差分约束的话,应该是ai-bj的形式,于是可以取对数

log(l')<=log(ai)-log(bj)<=log(u')

把log(ai)和log(bj)看成两个点ai和bj,化成求最短路的形式:dis[ai]-dis[bj]<=log(u'),dis[bj]-dis[ai]<=-log(l')

然后判负环就行

注意的是,如果spfa队列判负环:

(1)不必判断某个点入队次数大于N,只要判断是否大于sqrt(1.0*N)

(2)或者所有点的入队次数大于T*N,即存在负环,一般T取2

N为所有点的个数

const  int  maxn = 1008 ;
struct Edge{
       int v ;
       double w ;
       int next ;
}e[361000] ;

int    g[maxn]  , id ;
inline void add(int u , int v , double w){
       e[id].v = v ;
       e[id].w = w ;
       e[id].next = g[u] ;
       g[u] = id++ ;
}

int     Q[361000] ;
bool    in[maxn] ;
int     relax[maxn] ;
double  dist[maxn] ;
int   spfa(int N){
      int head = 0 , tail = -1 ;
      for(int i = 1 ; i <= N ; i++){//加入虚拟源点
            dist[i] = 0 ;
            Q[++tail] = i ;
            in[i] = 1 ;
            relax[i] = 1 ;
      }
      int limit =  (int)sqrt(0.5 + N) ;
      while(head <= tail){
           int u = Q[head++]  ;
           in[u] = 0 ;
           if(relax[u] > limit)  return 0 ;
           for(int i = g[u] ; i != -1 ; i = e[i].next){
                 int v = e[i].v  ;  ;
                 if(dist[u] + e[i].w < dist[v]){
                      dist[v] = dist[u] + e[i].w  ;
                      if(! in[v]){
                            in[v] = 1 ;
                            relax[v]++ ;
                            Q[++tail] = v ;
                      }
                 }
           }
      }
      return 1 ;
}

int main(){
    int n , m , i , j ;
    double L , U  , x ;
    while(scanf("%d%d%lf%lf" ,&n,&m,&L,&U) != EOF){
         id = 0 ;
         memset(g , -1 , (n+m+1)*sizeof(int)) ;
         L = log(L) ;
         U = log(U) ;
         for(i = 1 ; i <= n ; i++){
             for(j = 1 ; j <= m ; j++){
                 scanf("%lf" ,&x) ;
                 x = log(x) ;
                 add(n+j , i , U - x) ;
                 add(i , n+j , x - L) ;
             }
         }
         if(spfa(n+m)) puts("YES") ;
         else          puts("NO")  ;
    }
    return  0 ;
}


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

操作P : d[A] -d[ B] = x 
<=>   d[A]-d[B] >= x  且d[A]-d[B] <= x
=>     d[B] - d[A] <= -x  加边(A , B , -x) ;
         d[A]-d[B] <= x     加边(B , A , x) ;
操作V  :   d[A] - d[B] >=1  
=>         d[B] - d[A] <= -1  加边(A, B, -1) 
在spfa算法中,如果一个顶点入队列的次数超过n,则表示有向网中存在负权值回路。(《图论算法》)
const  int maxn = 1008 ;
struct Edge{
       int v ;
       int w ;
       int next ;
}e[400008] ;
int  id , g[maxn] ;
inline  void add(int u , int v , int w){
        e[id].v = v ;
        e[id].w = w ;
        e[id].next = g[u] ;
        g[u] = id++ ;
}

queue<int> Q ;
int  relax[maxn] ;
bool in[maxn] ;
int  dist[maxn] ;
int  spfa(int s , int N){
     while(! Q.empty()) Q.pop() ;
     for(int i = 0 ; i <= N ; i++){
           dist[i] = 100000000 ;
           in[i] = 0 ;
           relax[i] = 0 ;
     }
     dist[s] = 0 ;
     Q.push(s) ;
     while(! Q.empty()){
           int u = Q.front() ;  Q.pop() ;
           in[u] = 0 ;
           for(int i = g[u] ; i != -1 ; i = e[i].next){
                int v = e[i].v  ;
                if(dist[u] + e[i].w < dist[v]){
                      dist[v] = dist[u] + e[i].w ;
                      if(! in[v]){
                           in[v] = 1 ;
                           if(++relax[v] > N+1)  return 0 ;
                           Q.push(v) ;
                      }
                }
           }
     }
     return 1 ;
}

int  main(){
     int i , n , m , a , b , c ;
     char s[2] ;
     while(cin>>n>>m){
          memset(g , -1 , (n+1)*sizeof(int)) ;
          id = 0  ;
          while(m--){
               scanf("%s" , s) ;
               if(s[0] == 'P'){
                    scanf("%d%d%d" , &a , &b , &c) ;
                    add(b , a , c) ;
                    add(a , b , -c) ;
               }
               else{
                    scanf("%d%d" , &a , &b) ;
                    add(a , b , -1) ;
               }
          }
          for(i = 1 ; i <= n ; i++)  add(0 , i , 0) ;
          if(spfa(0 , n))  puts("Reliable")  ;
          else             puts("Unreliable") ;
     }
     return 0 ;
}

给出n个变量,m个约束公式 Sa + Sa+1 + .... + Sa+b < ki or > ki ,叫你判断是否存在着解满足这m组约束公式。(poj) 

Sa + Sa+1   +   .+ Sa+b =  Sum[a+b] - Sum[a-1]  . 注意加入源点n+1 。
const  int maxn = 1008 ;
struct Edge{
       int v ;
       int w ;
       int next ;
}e[400008] ;
int  id , g[maxn] ;
inline  void add(int u , int v , int w){
        e[id].v = v ;
        e[id].w = w ;
        e[id].next = g[u] ;
        g[u] = id++ ;
}

queue<int> Q ;
int  relax[maxn] ;
bool in[maxn] ;
int  dist[maxn] ;
int  spfa(int s , int N){
     while(! Q.empty()) Q.pop() ;
     for(int i = 0 ; i <= N ; i++){
           dist[i] = 100000000 ;
           in[i] = 0 ;
           relax[i] = 0 ;
     }
     dist[s] = 0 ;
     Q.push(s) ;
     while(! Q.empty()){
           int u = Q.front() ;  Q.pop() ;
           in[u] = 0 ;
           for(int i = g[u] ; i != -1 ; i = e[i].next){
                int v = e[i].v  ;
                if(dist[u] + e[i].w < dist[v]){
                      dist[v] = dist[u] + e[i].w ;
                      if(! in[v]){
                           in[v] = 1 ;
                           if(++relax[v] > N)  return 0 ;
                           Q.push(v) ;
                      }
                }
           }
     }
     return 1 ;
}

int  main(){
     int i , n , m , a , b , k ;
     char s[2] ;
     while(cin>>n && n){
          cin>>m ;
          memset(g , -1 , (n+2)*sizeof(int)) ;
          id = 0  ;
          while(m--){
               scanf("%d%d%s%d" , &a , &b , s , &k) ;
               if(s[0] == 'l')  add(a-1 , a+b , k-1) ;
               else             add(a+b , a-1 , -1-k) ;
          }
          for(i = 0 ; i <= n ; i++)  add(n+1 , i , 0) ;
          if(spfa(n+1 , n+2))  puts("lamentable kingdom")  ;
          else                 puts("successful conspiracy") ;
     }
     return 0 ;
}

A <= Xl + Xl+1 +...+  Xl+r   <= B  ,-10000<=xi<=10000输出字典序最大的解X 。(zoj) 

 图肯定连通,源点从0开始即可。 
字典序最大的解。   X1 - X0(源点) >= M   , 也就是这个M最大, Xi答案就是S[i] - S[i-1]
const  int maxn = 1008 ;
struct Edge{
       int v ;
       int w ;
       int next ;
}e[400008] ;
int  id , g[maxn] ;
inline  void add(int u , int v , int w){
        e[id].v = v ;
        e[id].w = w ;
        e[id].next = g[u] ;
        g[u] = id++ ;
}

queue<int> Q ;
int  relax[maxn] ;
bool in[maxn] ;
int  dist[maxn] ;
int  spfa(int s , int N){
     while(! Q.empty()) Q.pop() ;
     for(int i = 0 ; i <= N ; i++){
           dist[i] = 100000000 ;
           in[i] = 0 ;
           relax[i] = 0 ;
     }
     dist[s] = 0 ;
     Q.push(s) ;
     while(! Q.empty()){
           int u = Q.front() ;  Q.pop() ;
           in[u] = 0 ;
           for(int i = g[u] ; i != -1 ; i = e[i].next){
                int v = e[i].v  ;
                if(dist[u] + e[i].w < dist[v]){
                      dist[v] = dist[u] + e[i].w ;
                      if(! in[v]){
                           in[v] = 1 ;
                           if(++relax[v] > N)  return 0 ;
                           Q.push(v) ;
                      }
                }
           }
     }
     return 1 ;
}

int  main(){
     int i , n , m , a , b ,  l , r , ok ;
     char s[2] ;
     while(cin>>n>>m){
          memset(g , -1 , (n+2)*sizeof(int)) ;
          id = 0  ;
          ok = 0 ;
          while(m--){
               scanf("%d%d%d%d" , &l , &r , &a , &b) ;
               add(r , l-1 , -a) ;
               add(l-1 , r , b) ;
               if(l > r)  ok = 1 ;
          }
          if(ok){
                puts("The spacecraft is broken!") ;
                continue ;
          }
          for(i = 1 ; i <= n ; i++){
               add(i-1 , i , 10000) ;
               add(i, i-1 , 10000)  ;
          }
          if(spfa(0 , n+1)){
                printf("%d" , dist[1] - dist[0]) ;
                for(i = 2 ; i <= n ; i++) printf(" %d" , dist[i] - dist[i-1]) ;
                puts("") ;
          }
          else puts("The spacecraft is broken!") ;
     }
     return 0 ;
}

1必须站在0号位置, Pos[B] - Pos[A] >= C    ,Pos[B] - Pos[A] <= C ,  i必须站在i-1的右边, n站的位置小于100000000 。求1-n 可站位置的区间。 fzu

最短路 求最大值
构造  d[i] -  d[j] <= c  加边 (j , i , c)
最长路 求最小值
构造  d[i] - d[j] >=c  加边(j , i  ,  c)
const  int maxn = 1008 ;
struct Edge{
       int v ;
       int w ;
       int next ;
}e[10008] , e2[10008] ;
int  id , g[maxn] ;
inline  void add(int u , int v , int w){
        e[id].v = v ;
        e[id].w = w ;
        e[id].next = g[u] ;
        g[u] = id++ ;
}

queue<int> Q ;
int  relax[maxn] ;
bool in[maxn] ;
int  dist[maxn] ;
int  spfa(int s , int N){
     while(! Q.empty()) Q.pop() ;
     for(int i = 0 ; i <= N ; i++){
           dist[i] = 1000000000 ;
           in[i] = 0 ;
           relax[i] = 0 ;
     }
     dist[s] = 0 ;
     Q.push(s) ;
     while(! Q.empty()){
           int u = Q.front() ;  Q.pop() ;
           in[u] = 0 ;
           for(int i = g[u] ; i != -1 ; i = e[i].next){
                int v = e[i].v  ;
                if(dist[u] + e[i].w < dist[v]){
                      dist[v] = dist[u] + e[i].w ;
                      if(! in[v]){
                           in[v] = 1 ;
                           if(++relax[v] > N)  return 0 ;
                           Q.push(v) ;
                      }
                }
           }
     }
     return 1 ;
}

int  spfa2(int s , int N){
     while(! Q.empty()) Q.pop() ;
     for(int i = 0 ; i <= N ; i++){
           dist[i] = -1000000000 ;
           in[i] = 0 ;
           relax[i] = 0 ;
     }
     dist[s] = 0 ;
     Q.push(s) ;
     while(! Q.empty()){
           int u = Q.front() ;  Q.pop() ;
           in[u] = 0 ;
           for(int i = g[u] ; i != -1 ; i = e[i].next){
                int v = e[i].v  ;
                if(dist[u] + e[i].w > dist[v]){
                      dist[v] = dist[u] + e[i].w ;
                      if(! in[v]){
                           in[v] = 1 ;
                           if(++relax[v] > N)  return 0 ;
                           Q.push(v) ;
                      }
                }
           }
     }
     return 1 ;
}

int  minans[maxn] , maxans[maxn] ;
int  la[maxn] , lb[maxn] , lc[maxn] ;
int  da[maxn] , db[maxn] , dc[maxn] ;

int  main(){
     int i ,t ,  n , ml , md , a , b , c , T = 1 ;
     cin>>t ;
     while(t--){
          cin>>n>>ml>>md ;
          memset(g , -1 , (n+2)*sizeof(int)) ;
          id = 0  ;
          for(i = 1 ; i <= ml ; i++){
               scanf("%d%d%d" ,&a , &b , &c) ;
               add(a , b , c) ;
               la[i] = a  , lb[i] = b , lc[i] = c ;
          }
          for(i = 1 ; i <= md ; i++){
               scanf("%d%d%d" ,&a , &b , &c) ;
               add(b , a , -c) ;
               da[i] = a , db[i] = b , dc[i] = c ;
          }
          for(i = 2 ; i <= n ; i++)  add(i , i-1 , -1) ;
          add(0 , 1 , 0) ;
          add(1 , 0 , 0) ;
          add(0 , n , 100000000) ;
          printf("Case #%d: " , T++) ;
          if(spfa(0 , n+1)){
                for(i = 1 ; i <= n ; i++) maxans[i] = dist[i] ;
          }
          else{
              puts("Not Exist!") ;
              continue  ;
          }
          memset(g , -1 , (n+2)*sizeof(int)) ;
          id = 0  ;
          for(i = 1 ; i <= ml ; i++)  add(lb[i] , la[i] , -lc[i]) ;
          for(i = 1 ; i <= md ; i++)  add(da[i] , db[i] , dc[i]) ;
          for(i = 2 ; i <= n ; i++)   add(i-1 , i , 1) ;
          add(0 , 1 , 0) ;
          add(1 , 0 , 0) ;
          add(n , 0 , -100000000) ;
          if(spfa2(0 , n+1)){
                for(i = 1 ; i <= n ; i++) minans[i] = dist[i] ;
          }
          else{
              puts("Not Exist!") ;
              continue  ;
          }
          puts("Exist!") ;
          for(i = 1 ; i <= n ; i++)  printf("%d %d\n" , minans[i] , maxans[i]) ;
     }
     return 0 ;
}



你可能感兴趣的:(差分约束系统总结)