如果一个系统由n个变量和m个约束条件组成,形成m个形如ai-aj≤k的不等式(i,j∈[1,n],k为常数),则称其为差分约束系统
举一个例子,给定n个变量和m个不等式,每个不等式的形式为x[i]-x[j]<=a[k](0<=i,j<n,0<=k<m,a[k]已知)求x[i]-x[j]的最大值
为了解决这一类问题,需要先将所有的约束条件统一一下格式:
如果需要求的是两个变量差的最大值,那么需要将所有不等式转变成"<=",建图后求最短路
对于每个不等式x[i]-x[j]<=a[k],对结点j和i建立一条j->i的有向边,边权为a[k],求x[n-1]-x[0]的最大值就是求0到n-1的最短路
如果需要求的是两个变量差的最小值,那么需要将所有不等式转化成">=",建图后求最长路
下面一BZOJ2330为例,这是一个可以转化为差分约束问题的求最小值的一个例子
const int maxn=100005; int n,k,cnt; long long ans; int q[maxn],head[maxn],d[maxn],cir[maxn]; bool inq[maxn]; struct data{int to,next,v;}e[4*maxn]; void insert(int u,int v,int w) {e[++cnt].to=v;e[cnt].next=head[u];e[cnt].v=w;head[u]=cnt;}
n个小朋友,k个限制条件,cnt是邻接表边的数量
ans是最终的答案,q,head,d是最短路相关的数组(此处是求的最长路),cir是用来统计每个点的入队次数的,用来判环
bool spfa() { int h=0,t=1,now; q[t]=0,inq[0]=1;cir[0]=1; while(h!=t) { h=h%maxn+1; now=q[h]; for(int i=head[now];i;i=e[i].next) { if(d[now]+e[i].v>d[e[i].to]) { d[e[i].to]=d[now]+e[i].v; //最长路 if(++cir[e[i].to]>=n) return 0; if(!inq[e[i].to]) { inq[e[i].to]=1; t=t%maxn+1;q[t]=e[i].to; } } } inq[now]=0; } return 1; }
这里的spfa是可以判环的,并且求的是最长路,这点需要注意
然后,针对每一个限制条件:
case 1:insert(a,b,0);insert(b,a,0);break;//b>=a a>=b -> a=b case 2:if(a==b){printf("-1");return 0;} insert(a,b,1);break;//b>=a+1 -> b>a case 3:insert(b,a,0);break;//a>=b case 4:if(a==b){printf("-1");return 0;} insert(b,a,1);break;//a>=b+1 -> a>b case 5:insert(a,b,0);break;
因为题目要求的是,所有人分得糖果的和的最小值,那么需要假设一个虚拟节点0,然后以0作为源点计算从源点到所有点的最长路
之后我统计d数组就可以得到0到每一个节点的距离,这个距离值可以转化成差分约束条件:
求A-B的最小值就是求B到A的最长路,那么本题中,计算的任一点x到0的最长路dx也就是x-0>=dx,那么就有x>=dx,dx就因此成了x的下界
遇到最值问题的时候,找到合适的源点,分清是求最大值还是最小值,将差分约束条件进行合理地转化是解决问题的关键
这里给出利用差分约束系统求最大值并结合最“短”路模型时的几种约束条件的变式:
X[n-1]-X[0]>=T ,可以进行移项转化为: X[0]-X[n-1]<=-T。 X[n-1]-X[0]<T, 可以转化为X[n-1]-X[0]<=T-1。 X[n-1]-X[0]=T,可以转化为X[n-1]-X[0]<=T&&X[n-1]-X[0]>=T,再利用第一种方式进行转化即可
下面给出题目完整的实现:
1 #include<cstdio> 2 const int maxn=100005; 3 int n,k,cnt; 4 long long ans; 5 int q[maxn],head[maxn],d[maxn],cir[maxn]; 6 bool inq[maxn]; 7 struct data{int to,next,v;}e[4*maxn]; 8 void insert(int u,int v,int w) 9 {e[++cnt].to=v;e[cnt].next=head[u];e[cnt].v=w;head[u]=cnt;} 10 bool spfa() 11 { 12 int h=0,t=1,now; 13 q[t]=0,inq[0]=1;cir[0]=1; 14 while(h!=t) 15 { 16 h=h%maxn+1; 17 now=q[h]; 18 for(int i=head[now];i;i=e[i].next) 19 { 20 if(d[now]+e[i].v>d[e[i].to]) 21 { 22 d[e[i].to]=d[now]+e[i].v; //最长路 23 if(++cir[e[i].to]>=n) return 0; 24 if(!inq[e[i].to]) 25 { 26 inq[e[i].to]=1; 27 t=t%maxn+1;q[t]=e[i].to; 28 } 29 } 30 } 31 inq[now]=0; 32 } 33 return 1; 34 } 35 int main() 36 { 37 scanf("%d%d",&n,&k); 38 int x,a,b; 39 while(k--) 40 { 41 scanf("%d%d%d",&x,&a,&b); 42 switch(x) 43 { 44 case 1:insert(a,b,0);insert(b,a,0);break;//b>=a a>=b -> a=b 45 case 2:if(a==b){printf("-1");return 0;} 46 insert(a,b,1);break;//b>=a+1 -> b>a 47 case 3:insert(b,a,0);break;//a>=b 48 case 4:if(a==b){printf("-1");return 0;} 49 insert(b,a,1);break;//a>=b+1 -> a>b 50 case 5:insert(a,b,0);break; 51 } 52 } 53 for(int i=n;i>=1;i--) insert(0,i,1);//i>=0+1 -> i>0 54 if(!spfa()) {printf("-1");return 0;} //有环 55 for(int i=1;i<=n;i++) ans+=d[i]; //每一个点与0的距离就是每一个人分到的糖果数 56 printf("%lld",ans); 57 return 0; 58 }