差分约束
差分约束系统是什么?
差分约束系统指的是一个序列 \(x={x_1,x_2,\cdots x_m}\) 和以如下形式出现的 \(n\) 元一次不等式组。
下面给出几道例题。
还是说说前置知识。
P3385 【模板】负环
Description
给你一个 \(n\) 个点的有向图,让你判断图中是否有能从 \(1\) 号点出发可以到达的负环。
负环定义为一条边权和为负的回路。
Solution
思考负环的一些性质。
容易发现负环上一点与任意一点的最短路都不存在,因为你可以跑无数遍负环,边权和会越来越大。我们需要做的是找出一个限度值,如果超过了这个限度值就知道出现了负环。
注意到跑完一次单源最短路最多也就是把所有节点全部入队更新。如果点数为 \(n\),同一个节点最多只可能入队 \(n-1\) 次。
所以,开一个数组 cnt 记录每个节点入队的次数,如果次数超过 \(n-1\) 就说明有负环。
SPFA 在卡满情况下与朴素 Bellman-Ford 复杂度都为 \(O(nm)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
long long T,n,m,tot,head[2005],dis[2005],cnt[2005];
bool vis[2005];
queue<int>q;
struct node{
int from,to,w,nxt;
}e[6005];
inline void add_edge(int u,int v,int w){
e[++tot].from=u;
e[tot].to=v;
e[tot].w=w;
e[tot].nxt=head[u];
head[u]=tot;
return;
}
inline bool SPFA(int s){
memset(dis,63,sizeof(dis));
dis[s]=0;
vis[s]=1;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to,w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v]){
cnt[v]++;
if(cnt[v]>=n){
return 1;
}
vis[v]=1;
q.push(v);
}
}
}
}
return 0;
}
signed main(){
cin>>T;
while(T--){
tot=0;
memset(head,0,sizeof(head));
memset(e,0,sizeof(e));
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
while(!q.empty()){
q.pop();
}
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
if(w>=0){
add_edge(u,v,w);
add_edge(v,u,w);
}
else{
add_edge(u,v,w);
}
}
if(SPFA(1)){
cout<<"YES"<<endl;
}
else{
cout<<"NO"<<endl;
}
}
return 0;
}
P5960 【模板】差分约束
Description
给你一个差分约束系统,让你判断这个差分约束系统是否存在合法的解,如果存在,输出任意一组可行解。
\(1\le n,m \le 5\times 10^3\)
Solution
我们单独把一个式子拿出来看:
移项:
瞬间想起了一些不好的东西。
int v=e[i].to,w=e[i].w;
if(dis[v]>=dis[u]+w){
dis[v]=dis[u]+w;
}
对没错这个东西很像最短路中的三角形不等式:\(\text{dis[v]}\le \text{dis[u]+w}\)。
考虑把三角形不等式稍作改动:\(\text{dis[i]}\le \text{dis[j]+k}\)。最终的最短路径中每条边都要满足这个不等式,而差分约束的最终可行解每个 \(x_i\) 也都要满足这个不等式。
因此,对于每一个不等式,我们考虑建一条从 \(j\) 到 \(i\),长度为 \(c_k\) 的有向边。
我们先建立一个超级源点,不妨设为 \(0\) 号点。从 \(0\) 号点向所有点连一条长度为 \(0\) 的边,跑最短路即可。
(Tips:差分约束的式子也可以拿来跑最长路)
我们不清楚图的具体形态,所以要注意判断负环。当存在负环时(大多数时候使用 SPFA 来做),差分约束系统无解。
最短路结束之后,若差分约束系统有可行解,一组可行解即为 \(x_i=dis_i\)。
复杂度就是 SPFA 的复杂度,最坏 \(O(nm)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,m,tot,head[10005],dis[10005],cnt[10005];
bool vis[10005];
struct node{
int from,to,w,nxt;
}e[20005];
inline void add_edge(int u,int v,int w){
e[++tot].from=u;
e[tot].to=v;
e[tot].w=w;
e[tot].nxt=head[u];
head[u]=tot;
return;
}
queue<int>q;
inline bool SPFA(){
vis[0]=1;
q.push(0);
memset(dis,63,sizeof(dis));
dis[0]=0;
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=e[i].nxt){
int u=x,v=e[i].to,w=e[i].w;
if(dis[v]>=dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v]){
cnt[v]++;
vis[v]=1;
if(cnt[v]>n){
return 1;
}
q.push(v);
}
}
}
}
return 0;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
add_edge(0,i,0);
}
for(int i=1;i<=m;i++){
int x,y,k;
cin>>x>>y>>k;//x-y<=k -> x<=y+k
add_edge(y,x,k);
}
if(SPFA()){
cout<<"NO"<<endl;
}
else{
for(int i=1;i<=n;i++){
cout<<dis[i]<<" ";
}
}
return 0;
}
P1993 小 K 的农场
Description
给你 \(m\) 个形式如下的不等式,让你判断这个 \(n\) 元一次不等式组是否存在合法的正整数解。
-
1 a b c表示 \(A_a-A_b\ge c\)。 -
2 a b c表示 \(A_a-A_b\le c\)。 -
3 a b表示 \(A_a=A_b\)。
Solution
想办法转化一下。
对于操作 1:
两边同时加 \(A_b\):
移项成一个差分约束的形式即可。
加边的代码:add_edge(a,b,-c)。
同理,操作二可化为 add_edge(b,a,c)。
操作三,\(A_a=A_b\)。
可以分成两个不等式来看:\(A_a\ge A_b\) 且 \(A_a\le A_b\)。
add_edge(a,b,0);
add_edge(b,a,0);
Tips:由于最后一个操作会建两条边,所以最坏情况下边数是 \(2\times m+n\) 的,注意边数组的大小。
#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,m,tot,head[10005],vis[10005],dis[10005],cnt[10005];
struct node{
int from,to,w,nxt;
}e[15005];
inline void add_edge(int u,int v,int w){
e[++tot].from=u;
e[tot].to=v;
e[tot].w=w;
e[tot].nxt=head[u];
head[u]=tot;
return;
}
queue<int>q;
inline bool SPFA(){
memset(dis,63,sizeof(dis));
dis[0]=0;
vis[0]=1;
q.push(0);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to,w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v]){
vis[v]=1;
cnt[v]++;
if(cnt[v]>=n){
return 1;
}
q.push(v);
}
}
}
}
return 0;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
add_edge(0,i,0);
}
for(int i=1;i<=m;i++){
int opt;
cin>>opt;
if(opt==1){
int a,b,c;//b<=a-c
cin>>a>>b>>c;
add_edge(a,b,-c);
}
else if(opt==2){
int a,b,c;//a<=b+c
cin>>a>>b>>c;
add_edge(b,a,c);
}
else{
int a,b;
cin>>a>>b;//b<=a+0 && a<=b+0
add_edge(a,b,0);
add_edge(b,a,0);
}
}
if(SPFA()){
cout<<"No"<<endl;
}
else{
cout<<"Yes"<<endl;
}
return 0;
}
P4926 [1007] 倍杀测量者
Description
给出 \(s\) 个条件和 \(t\) 个数组 \(A\) 中的值,让你求出最大的正实数 T,使得不全满足条件。
-
1 a b k:\(A_a\ge (k-T)\times A_b\) -
2 a b k:\(A_b < (k+T)\times A_a\)
\(1\le n,s\le 10^3\)。
Solution
容易想到二分 \(T\),主要讲讲如何转化条件。
opt 1
add_edge(b,a,log(k-T));
opt 2
add_edge(b,a,-log(k+T))
正确性被题目中的 1e-4 保证了。
最坏复杂度为 \(O(nm)\),可以通过。
Tips:如果 \(T=0\) 时却能满足所有条件,输出 -1。

浙公网安备 33010602011771号