差分约束系统:
假设有不等式组:
- 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;
}