差分约束

差分约束系统:

假设有不等式组:

  • x[1] - x[2] <= 0
  • x[1] - x[5] <= -1
  • x[2] - x[5] <= 1
  • x[3] - x[1] <= 5
  • x[4] - x[1] <= 4
  • x[4] - x[3] <= -1
  • x[5] - x[3] <= -3
  • x[5] - x[4] <= -3

其中每个不等式都是两个未知数的差小于等于(或大于等于)某个常数
这样的不等式组称为差分约束系统

差分约束系统的解:

可以验证,上述不等式组的一组解为{-5,-3,0,-1,-4}。

同时,任意{x[i]+d}都是该不等式组的一组解,例如{x[i]+1}={-1,-2,1,0,-3}也是该不等式组的一组解
证明:设不等式为x-y<=c(其中x,y为未知数,c为常数),因为x-y=(x+d)-(y+d),所以若x-y<=c成立,则(x+d)-(y+d)<=c也成立,所以任意{x[i]+d}都是该不等式组的一组解。

由此可见,差分约束系统要么有无穷多组解,要么就无解

差分约束系统与单源最短路中的三角形不等式:

单源最短路中的dist数组满足三角形不等式:d[v]<=d[u]+e(u,v)。
如果不满足,则可以用d[u]+e(u,v)来更新d[v]。

把上面的不等式稍稍修改一下,变成d[v]-d[u]<=e(u,v)。
是不是和差分约束系统中的不等式非常像?(简直一模一样)。

因此一个差分约束系统可以用一个有向图来表示:
差分约束系统中的每个变量x[i]都代表一个顶点V[i]。
每个不等式x[i]-x[j]<=c都可以转化为三角形不等式x[i]<=x[j]+c,在有向图中代表顶点V[j]到顶点V[i]有一条边权为c的路径。

利用所有不等式建图,就能得到一个有向图。
但是这样建成的有向图并不能保证是联通的,所以还需要添加一个源点V[0],V[0]向所有节点V[i]建立一条边权为0的路径。
至此,一个联通的有向图才真正构造完成。

由上可知,每条边都对应着差分约束系统中的一个不等式。计算源点V[0]到其他所有点的单源最短路径,由最短路算法可知,计算出来的dist数组一定满足三角形不等式,所以dist数组中的元素值符合差分约束系统中的所有不等式,因此可以得出dist数组就是差分约束系统的一组解。用这一组解就能计算出无数解。

那么什么时候才无解呢?是无法计算出最短路的情况,也就是该有向图存在一个负权回路,使得最短路径长度可以无限缩小。此时是无解的。因为图中可能存在负权回路,所以在计算最短路的时候必须使用能处理负边权的算法:bellman-ford或spfa


2019CCPC哈尔滨 A.Artful Paintings

题意:

有n个格子,你可以给每个各自染色,但是要满足M种条件:
1.区间[L,R]内的染色格子数不少于K。
2.区间[L,R]外的染色格子数不少于K。
求满足全部条件的最小染色数
n,m1,m2<=3e3

思路:

题目条件为不少于,如果染色X个满足,则染色X+1个也满足,具有单调性,因此考虑二分。
考虑前缀和形式,S(x)表示前x个格子的染色数,则:
1.S(R )-S(L-1)>=k
2.S(R )-S(L-1)<=mid-k
3.S(i)-S(i-1)>=0
4.S(i)-S(i-1)<=1
5.S(n)-S(0)<=mid
6.S(0)-S(n)<=-mid
建图跑spfa即可。
但是只是这样还是会超时。
考虑如果发现某一个点最短路距离已经为负数,那么必定存在负环,这个时候可以直接退出。
因为存在i向i−1连0的边。那么先从0到那个负权点然后再绕一圈返回到0一定是负环。

ps:
注意边数组大小
多加几组必须满足但是不确定有没有用的不等式,似乎避免遗漏。

code:
//https://codeforces.com/gym/102394/problem/A
#include<bits/stdc++.h>
using namespace std;
const int maxm=3e3+5;
int head[maxm],nt[maxm<<2],to[maxm<<2],w[maxm<<2],cnt;
int mark[maxm];
int num[maxm];
int d[maxm];
struct Node{
    int l,r,k;
}e[maxm],ee[maxm];
int n,m1,m2;
void init(){
    for(int i=0;i<=n;i++)head[i]=0;
    cnt=1;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool spfa(int st){
    for(int i=0;i<=n;i++){
        mark[i]=0;
        d[i]=1e9;
        num[i]=0;
    }
    queue<int>q;
    q.push(st);
    mark[st]=1;
    num[st]=1;
    d[st]=0;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        mark[x]=0;
        for(int i=head[x];i;i=nt[i]){
            int v=to[i];
            if(d[v]>d[x]+w[i]){
                d[v]=d[x]+w[i];
                if(d[v]<0)return 0;//如果某个点的最短路距离已经是负数说明出现负环
                if(!mark[v]){
                    mark[v]=1;
                    q.push(v);
                    if(++num[v]>n)return 0;//如果出现负环,则无解
                }
            }
        }
    }
    return 1;
}
bool check(int mid){
    init();
    for(int i=1;i<=n;i++){
        add(i-1,i,1);
        add(i,i-1,0);
    }
    for(int i=1;i<=m1;i++){
        add(e[i].r,e[i].l-1,-e[i].k);
    }
    for(int i=1;i<=m2;i++){
        add(ee[i].l-1,ee[i].r,mid-ee[i].k);
    }
    add(0,n,mid);
    add(n,0,-mid);
    return spfa(0);
}
signed main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d",&n,&m1,&m2);
        for(int i=1;i<=m1;i++){
            scanf("%d%d%d",&e[i].l,&e[i].r,&e[i].k);
        }
        for(int i=1;i<=m2;i++){
            scanf("%d%d%d",&ee[i].l,&ee[i].r,&ee[i].k);
        }
        int ans=n;
        int l=0,r=n;
        while(l<=r){
            int mid=(l+r)/2;
            if(check(mid)){
                ans=mid;
                r=mid-1;
            }else{
                l=mid+1;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

ZOJ2770 Burn the Linked Camp

题意:

给n,m
n个兵营,每个兵营容量为c(i)
m个条件,每个条件为区间[x,y]的人数不能少于z
问最少需要多少人能满足所有条件

思路:

设S(x)为1-x的前缀和
不等式组:
1.S(y)-S(x-1)>=z,题目条件
2.S(y)-S(x-1)<=c(x)+c(x+1)…+c(y),区间总人数不能超过区间总容量
3.S(i)-S(i-1)<=c(i),单个位置的人数不能超过该位置的容量
4.S(i)-S(i-1)>=0,人数不能为负

因为要求的是最少需要多少人,也就是S(n)-S(0)>=ans中ans的最大值,
移位一下变为S(0)-S(n)<=-ans,答案为n到0的最短路距离的相反数。

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e4+5;
int head[maxm],nt[maxm<<2],to[maxm<<2],w[maxm<<2],cnt;
int mark[maxm],num[maxm],d[maxm];
int c[maxm];
int n,m;
void init(){
    memset(head,0,sizeof head);
    cnt=0;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool spfa(int st){
    for(int i=0;i<=n;i++){
        mark[i]=0;
        d[i]=1e9;
        num[i]=0;
    }
    queue<int>q;
    q.push(st);
    d[st]=0;
    mark[st]=1;
    num[st]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        mark[x]=0;
        for(int i=head[x];i;i=nt[i]){
            int v=to[i];
            if(d[v]>d[x]+w[i]){
                d[v]=d[x]+w[i];
                if(!mark[v]){
                    mark[v]=1;
                    q.push(v);
                    if(++num[v]>n)return 0;
                }
            }
        }
    }
    return 1;
}
signed main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        init();
        for(int i=1;i<=n;i++){
            scanf("%d",&c[i]);
            add(i-1,i,c[i]);
            add(i,i-1,0);
        }
        for(int i=1;i<=n;i++){//前缀和
            c[i]+=c[i-1];
        }
        for(int i=1;i<=m;i++){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            add(y,x-1,-z);
            add(x-1,y,c[y]-c[x-1]);
        }
        if(!spfa(n)){
            puts("Bad Estimations");
        }else{
            printf("%d\n",-d[0]);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值