差分约束
前言:
可能是因为菜得没边吧,这点东西折腾了我快一上午的时间去学。大概写一下个人理解,可能会有一些问题。欢迎指出问题并补充遗漏哦~~
概念:(\(by:oiwiki\))
差分约束系统 是一种特殊的\(n\)元一次不等式组,它包含\(n\)个变量\(x_1,x_2,...,x_n\)以及\(m\)个约束条件,每个约束条件是由两个其中的变量作差构成的,形如\(x_i-x_j≤c_k\),其中\(1≤i,j≤n,1≤k≤m\)并且\(c_k\)是常数(可以是非负数,也可以是负数)。我们要解决的问题是:求一组解\(x_1=a_1,x_2=a_2,...x_n=a_n\),使得所有的约束条件得到满足,否则判断出无解。
每个约束条件\(x_i-x_j≤c_k\)都可以变成\(x_i≤x_j+c_k\),这与单源最短路中的三角形不等式(松弛)\(dis_y≤dis_x+val_i\)非常相似。因此,我们可以把每个变量\(x_i\)看做是图上的结点,对于每个约束条件\(x_i-x_j≤c_k\),从节点\(j\)指向节点\(i\)连一条长度为\(c_k\)的有向边。
个人理解:
差分约束就是一个将数论问题转化为图论问题的途径,它将变量转化为节点,将约束关系转化为有向边。关键词:不大于,不小于,大于,小于等。
实现过程:
首先从题目中找出约束关系并转化为上述形式(其实不转也行,就是可能会麻烦一点,容易乱),然后建图,最后跑一遍单源最短路(我看网上大部分都是\(spfa\),不过也有\(dij\),具体情况具体分析吧),找一下有没有环以此来判断有没有解。然后就没啦~~
例题:
\(First:\)P1993 小 K 的农场
思路:
先找约束关系,题中给了三个条件:
-
\(x_a-x_b≥c_k\) .
-
\(x_a-x_b≤c_k\) .
-
\(x_a=x_b\) .
然后转化为统一形式:
-
\(x_b-x_a≤-c_k\) .
-
\(x_a-x_b≤c_k\) .
-
\(x_a-x_b≤0\) 且 \(x_a-x_b≥0\) .
接着进行建边:
-
\(add(a,b,-c)\) .
-
\(add(b,a,c)\) .
-
\(add(a,b,0)\) , \(add(b,a,0)\) .
最后再跑一遍\(spfa\)判断是否有负环即可,若有负环则无解。
代码:
#include<iostream>
#include<queue>
#include<cstring>
#define int long long
using namespace std;
const int N=5e4+5;
int m,n,op,a,b,c,cnt,head[N],tot[N<<1],dis[N];bool vis[N<<1];queue<int> q;
struct node{int val,to,nxt;}e[N<<1];
inline void add(int x,int y,int z){
e[++cnt].to=y;
e[cnt].val=z;
e[cnt].nxt=head[x];
head[x]=cnt;
}//建边
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>op;
if(op==1){
cin>>a>>b>>c;
add(b,a,c);
}else if(op==2){
cin>>a>>b>>c;
add(a,b,-c);
}else if(op==3){
cin>>a>>b;
add(a,b,0);
add(b,a,0);
}
}//约束关系
for(int i=1;i<=n;i++) add(0,i,0);
memset(dis,-0x3f,sizeof(dis));
dis[0]=0;vis[0]=1;q.push(0);
while(!q.empty()){
int now=q.front();q.pop();
vis[now]=0;
for(int i=head[now];i;i=e[i].nxt){
if(dis[e[i].to]<dis[now]+e[i].val){
dis[e[i].to]=dis[now]+e[i].val;
if(!vis[e[i].to]){
vis[e[i].to]=1;
q.push(e[i].to);
tot[e[i].to]++;
if(tot[e[i].to]>=n){
cout<<"No"<<'\n';
return 0;
}//有负环 无解
}
}
}
}//spfa跑单源最短路
cout<<"Yes"<<'\n';//无负环 有解
return 0;//完结撒花~~
}
\(tips:\)这道题输入顺序与题目描述的顺序不一样,别被坑了(别问我怎么知道的www)。但是数据比较水,1,2写反了也有90的高分!!
\(Second:\)P4926 [1007] 倍杀测量者
前言吐槽:
这波女生赢麻了,既没有惩罚还能看乐子
思路:
先二分查找\(T\)的值,然后用差分约束判断是否有解。
二分方面不必多说。差分约束方面还是先找约束关系,不过这题不太一样的是这道题的约束关系是除法。此题有两种处理方式:不等号左右分别取一个\(log\),然后就可以跟普通差分约束一样做差了(详情见高中数学);另一种则是就按照除法的方式建边,不过是松弛的式子微微有些调整。我用的是后一种办法。
由题可得约束关系:
-
\(\frac{x_a}{x_b}≥(k-T)\)
-
\(\frac{x_b}{x_a}<(k+T)\)
统一形式为:
-
\(x_a≥(k-T)x_b\)
-
\(x_a>\frac{x_b}{k+T}\)
继续建边:
-
\(add(b,a,k-T)\)
-
\(add(b,a,\frac{1}{k+T})\)
然后再跑\(spfa\)判负环就行啦~~
代码:
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=1024;
const double precision=1e-8;
int n,s,t,op[N],a[N],b[N],c[N],k[N],x[N],cnt,head[N],tot[N];bool vis[N];double l,r,dis[N];
struct node{int to,nxt;double val;}e[N<<1];
inline void add(int x,int y,double z){
e[++cnt].to=y;
e[cnt].val=z;
e[cnt].nxt=head[x];
head[x]=cnt;
}//建边
inline bool spfa(int now){
queue<int> q;
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis));
memset(tot,0,sizeof(tot));
q.push(now);vis[now]=1;dis[now]=1;//因为是乘法,初始值赋为1
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(e[i].val*dis[x]>dis[y]){//不一样的松弛
dis[y]=e[i].val*dis[x];
if(!vis[y]){
vis[y]=1;
q.push(y);
tot[y]++;
if(tot[y]>=n) return 1;//有环无解
}
}
}
vis[x]=0;
}
return 0;//无环有解
}
inline bool check(double T){
cnt=1;
memset(head,0,sizeof(head));
for(int i=1;i<=s;i++){
if(op[i]==1) add(b[i],a[i],k[i]-T);
else add(b[i],a[i],1.0/(k[i]+T));
}
for(int i=1;i<=t;i++){
add(0,c[i],x[i]);
add(c[i],0,1.0/x[i]);
}
return spfa(0);
}//二分+建边
int main(){
ios::sync_with_stdio(false);
cin>>n>>s>>t;
for(int i=1;i<=s;i++){
cin>>op[i]>>a[i]>>b[i]>>k[i];
r=max(r,(double)k[i]);
}
for(int i=1;i<=t;i++) cin>>c[i]>>x[i];
while(r-l>precision){
double mid=(l+r)/2;
if(check(mid)) l=mid;//无解
else r=mid;//有解
}//二分
if(l>precision) printf("%lf",l);//判断精度输出
else cout<<-1;//无解
return 0;//完结撒花~~
}
\(tips:\) 注意边权,\(dis\)数组,\(l\),\(r\),\(mid\),还有精度的类型都是\(int\)。细心一点哦~~
update:2025年11月26日
模板题
$code$
#include<iostream>
#include<queue>
using namespace std;
const int N=5e3+5;
int n,m,x,y,z,cnt,head[N],dis[N],num[N];
queue<int> q;
bool vis[N];
struct flower{
int to,nxt,val;
}e[N<<1];
inline void add(int x,int y,int z){
e[++cnt].to=y;
e[cnt].val=z;
e[cnt].nxt=head[x];
head[x]=cnt;
}
inline bool spfa(int s){
for(int i=0;i<=n;i++) dis[i]=1e9,vis[i]=0;
dis[s]=0;
vis[s]=1;
q.push(s);
while(!q.empty()){
int x=q.front();q.pop();
if(num[x]>n) return false;
vis[x]=0;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(dis[y]>dis[x]+e[i].val){
dis[y]=dis[x]+e[i].val;
if(!vis[y]){
num[y]++;
vis[y]=1;
q.push(y);
}
}
}
}
return true;
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++) add(0,i,0);
for(int i=1;i<=m;i++){
cin>>x>>y>>z;
add(y,x,z);
}
if(!spfa(0)) cout<<"NO"<<'\n';
else for(int i=1;i<=n;i++) cout<<dis[i]<<' ';
return 0;
}