差分约束
简介
差分约束系统是一种特殊的多元一次不等式组。
由 个点 , 个约束条件组成。对于每个约束条件,都是由其中的两个变量做差得到。例如 。 为常量。
我们需要将所有 解出来。差分约束问题的关键在于将所有的条件都建立成图中的边。
对于上式变号可得,。假设已知 的值,就可以算出 的上界。若对于 有多个约束条件,则 的范围就是最小的 。
如果我们将每个 抽象成点,每个约束条件成为一条从 ,边权为 的边,就是求最短路了。每次松弛就是更新 ,让 满足上式。
若对于该差分约束系统,有负环就意味着无解。因此,我们一般使用 算法求解。
为什么
理解为:每次松弛就是使 满足约束条件。无限的松弛说明不能满足条件。
同样的,假如变形后是 ,我们就可以确定 的下界。对所有约束条件取最大。也即求最长路。
关于实现
有些时候条件不能将所有都 串联在一起。即图是不连通的,此时利用超级原点思想,找一个没有值的点( 等),对所有点连接一条权为 的边。使得一次最短路可以求全图解。
对于 算法,一般的判断负环方法是:对于最短路,最多将图中每个节点遍历,也就是经过了 条边。当经过的边数 时,就出现了负环。(超级原点设置后,会多出来一个点!)
练习
小 k 的农场
差分约束入门题。以下提供最短路的做法。题意:
- 如果每行的第一个数是 ,接下来有三个整数 ,表示农场 比农场 至少多种植了 个单位的作物;
- 如果每行的第一个数是 ,接下来有三个整数 ,表示农场 比农场 至多多种植了 个单位的作物;
- 如果每行的第一个数是 ,接下来有两个整数 ,表示农场 种植的的数量和 一样多。
分别解决:
- .变形为
- .变形为
- .变形为
我们确定了解的上界,接下来要对所有的取最小值,也就是跑最短路。
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+10;
int n,m;
int dis[N],num[N],vis[N];
int head[N],ne;
struct EDGE {int to,next,dis;}e[N<<1];
void ae(int from,int to,int dis) {e[++ne]={to,head[from],dis};head[from]=ne;}
string spfa(int root) {
memset(dis,0x3f,sizeof dis);
memset(num,0,sizeof num);
memset(vis,0,sizeof vis);
dis[root]=0;
stack<int>q;
q.push(root);
while(!q.empty()) {
int from=q.top();q.pop();
vis[from]=0;
for(int i=head[from];i;i=e[i].next) {
int to=e[i].to,td=e[i].dis;
if(dis[to]>dis[from]+td) {
dis[to]=dis[from]+td;
num[to]=num[from]+1;//记录经过的边数
if(num[to]==n+1) return "No";//判断负环
if(vis[to]==0) {q.push(to), vis[to]=1;}
}
}
}
int ans=0;
for(int i=1;i<=n;i++) {
ans+=dis[i];
}
return "Yes";
}
int main() {
cin>>n>>m;
for(int i=1,o,x,y,z;i<=m;i++) {
cin>>o>>x>>y;
if(o==1) cin>>z,ae(x,y,-z);
if(o==2) cin>>z,ae(y,x,z);
if(o==3) ae(y,x,0),ae(x,y,0);
}
for(int i=1;i<=n;i++) {//设立 0 为 超级原点
ae(0,i,0);
}
cout<<spfa(0);
return 0;
}
P2294 [HNOI2005] 狡猾的商人
本题差分条件为在 区间内,收入为整数 。
加入把每个月的收入使用前缀和。就会有 。
对于相等的约束条件,就要使上,下界重合,即让 都取到等号。
注意
。超级原点不能在 0。可以选在 。
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+10;
int n,m;
int dis[N],num[N],vis[N];
int head[N],ne;
struct EDGE {int to,next,dis;}e[N<<1];
void ae(int from,int to,int dis) {e[++ne]={to,head[from],dis};head[from]=ne;}
string spfa(int root) {
memset(dis,0x3f,sizeof dis);
memset(num,0,sizeof num);
memset(vis,0,sizeof vis);
dis[root]=0;
stack<int>q;
q.push(root);
while(!q.empty()) {
int from=q.top();q.pop();
vis[from]=0;
for(int i=head[from];i;i=e[i].next) {
int to=e[i].to,td=e[i].dis;
if(dis[to]>dis[from]+td) {
dis[to]=dis[from]+td;
num[to]=num[from]+1;//记录经过的边数
if(num[to]==n+1) return "No";//判断负环
if(vis[to]==0) {q.push(to), vis[to]=1;}
}
}
}
int ans=0;
for(int i=1;i<=n;i++) {
ans+=dis[i];
}
return "Yes";
}
int main() {
cin>>n>>m;
for(int i=1,o,x,y,z;i<=m;i++) {
cin>>o>>x>>y;
if(o==1) cin>>z,ae(x,y,-z);
if(o==2) cin>>z,ae(y,x,z);
if(o==3) ae(y,x,0),ae(x,y,0);
}
for(int i=1;i<=n;i++) {//设立 0 为 超级原点
ae(0,i,0);
}
cout<<spfa(0);
return 0;
}
INTERVAL - Intervals
本题涉及到差分约束的一个关键:题目可能有隐含条件,要将所有条件都建图。
除了最基本的条件,每个数最多被选一次,所以有 。
[1007]倍杀测量者
奇怪题目
本题中,以下讨论不需要女装的情况。
有两种不同的边:(A,B 表示选手的分数 )
-
A 选手 倍杀了 B。
-
A 选手没有被 B 选手 倍杀。
要求最大的 使方程无解, 的取值具有单调性。越小的 就能满足条件,使小值最大,用二分答案解决。
但是乘法不能使用差分约束,考虑转化为加减法。使用幂的性质。
可以将上式中的 转化为 。由于底数相同,只要比较指数大小,所以转变上式,得:
对于开始就确定的边权,看作第三种边,利用超级原点,得到 。转变为:
二分 ,如果有负环即合法。
实现
在实现中,由于有两种边有 的存在,所以最短路中的 直接存储取对数后的值。为满足该条件,建第三种边时需要先 。
对于 , 的值有所改变。先存 ,根据边的类型算出不同的值。
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+10;
int n,m,m2;
double dis[N];int num[N],vis[N];
int head[N],ne;
struct EDGE {int to,next;double dis;int typ;}e[N<<1];
void ae(int from,int to,double dis,int typ) {e[++ne]={to,head[from],dis,typ};head[from]=ne;}
int spfa(int root,double t) {
memset(dis,-0x3f,sizeof dis);
memset(num,0,sizeof num);
memset(vis,0,sizeof vis);
dis[root]=0;
stack<int>q;
q.push(root);
while(!q.empty()) {
int from=q.top();q.pop();
vis[from]=0;
for(int i=head[from];i;i=e[i].next) {
int to=e[i].to;double td=e[i].dis;
if(e[i].typ==2) td=-log2(td+t);
if(e[i].typ==1) td=log2(td-t);
if(dis[to]<dis[from]+td) {
dis[to]=dis[from]+td;
num[to]=num[from]+1;
if(num[to]>n+1) return 1;
if(vis[to]==0) {q.push(to), vis[to]=1;}
}
}
}
return 0;
}
double l,r=10,mid;
int main() {
cin>>n>>m>>m2;
for(int i=1,k,o,x,y;i<=m;i++) {
cin>>o>>x>>y>>k;
ae(y,x,k,o);
if(o==1) r=min(r,k+0.00);//不能使 k-T < 0
}
for(int i=1,x,k;i<=m2;i++) {
cin>>x>>k;
ae(0,x,log2(k),3);
ae(x,0,-log2(k),3);
}
while(l+1e-8<r) {
mid=(l+r)/2;
if(spfa(0,mid)) l=mid;
else r=mid;
}
if(l==0) cout<<-1;
else cout<<l;
return 0;
}