[笔记]差分约束系统
算法用途
当题面给出许多形如(x_i - x_j ≤ c_k(c为常数))的不等式时,并让你求出一组满足条件的解时,就可以运用差分约束系统.
算法描述
首先,我们对不等式进行变形,变成(x_i ≤ x_j + c_k)的形式,这时我们可以发现,这和求最短路时的式子((dis[v] ≥ dis[u] + w_{u→v})))很像,但我们发现这两个式子的不等号方向相反,但这其实并不影响我们用最短路的方法来解决问题。
首先我们假设有一个不等式组
那么要使(t_i)满足所有不等式并且取到最大值,则答案应该是(min(t_{j'},t_{j''},t_{j'''}) + b),因为如果要满足所有不等式则要取最小值(同小取小),实际情况(t_j)集合的元素可能不止(3)个,我们直接用(t_j)来表示所有(t_j)集合中的元素,则答案可以表示为(t_i = min(t_j + b)),这和(SPFA)中的式子((dis_{ti} = min(dis_{tj} + w_{i→j}))一样,因此我们证明了差分约束的问题可以用最短路来解决。具体方法是连一条从(j)到(i)的权值为(b)的有向边,再跑(SPFA)即可
通过上面的推理我们知道当我们跑最短路时,可以求出满足条件的最大解,那么我们可以同理推出当我们用(SPFA)跑最长路时可以求出满足条件的最小值,那么究竟什么时候跑最短路,什么时候跑最长路呢?
例题讲解
在第一题中,题目的要求与上文我们举的例子相符合,因此是跑最短路。
再来看第二题,题目要求的是让我们在所有区间(a_i)到(b_i)中均选出不少于(c_i)个数来满足要求。我们假设(t_i)表示的是在前(i)个数中选了(t_i)个,那么我们可以得到如下不等式组:
类比上文的建图方法,我们以同样的方式建边,但要注意的是区间(i→j)所有选的数的个数应该表示为(t_j - t_{i - 1})因为第(i)位的数字也可能被选中。但是这一个条件并不全, 我们知道一定会有(0≤t_i - t_{i+1} ≤ 1),因为可能存在第(i+1)位选或不选的情况,但由于一个建边的条件并不能满足两个不等式(上式可以拆分为两个不等式),所以我们通过两个条件来约束.根据拆分完的不等式来进行约束(t_{i+1}-t_i ≥ 0) → (t_{i+1} ≥ t_i + 0)因此从(i)连一条到(j)的权值为(0)的有向边;第二个不等式是(t_i - t_{i+1} ≤ 1) → (t_i ≤ t_{i+1} + 1),因此从(i+1)连一条到(i)的权值为(0)的有向边,这样就没有漏过任何一个约束条件了。
代码
T1
#include <bits/stdc++.h>
using namespace std;
struct node{int to,next,w;}e[50010];
int fir[50010],tot = 0,times[50010],n,m;
void add(int x,int y,int z){
tot++;
e[tot].w = z;
e[tot].to = y;
e[tot].next = fir[x];
fir[x] = tot;
return;
}
bool in[50010];
int dis[50010];
bool spfa(int x){
queue < int > q;
for(int i = 1;i <= n;i++)dis[i] = 1e9;
while(!q.empty())
q.pop();
memset(in,false,sizeof(in));
memset(times,0,sizeof(times));
in[x] = true;
times[x]++;
dis[x] = 0;
q.push(x);
while(!q.empty()){
int k = q.front();
q.pop();in[k] = false;
for(int i = fir[k];i;i = e[i].next){
if(dis[e[i].to] > dis[k] + e[i].w){
dis[e[i].to] = dis[k] + e[i].w;
if(!in[e[i].to]){
q.push(e[i].to);
times[e[i].to]++;
in[e[i].to] = true;
if(times[e[i].to] > n)
return false;
}
}
}
}
return true;
}
int main(){
cin>>n>>m;
for(int i = 1;i <= m;i++){
int x,y,z;
cin>>x>>y>>z;
add(y,x,z);
}
for(int i = 1;i <= n;i++){
add(0,i,0);
}
if(!spfa(0)){
cout<<"NO"<<endl;
return 0;
}
for(int i = 1;i <= n;i++){
cout<<dis[i]<<" ";
}
cout<<endl;
return 0;
}
T2
#include <bits/stdc++.h>
using namespace std;
struct node{
int to,next,w;
}edge[150010];
const int inf = INT_MAX;
long long fir[150010],tim[150010],dis[150010],tot,n,maxx,minn;
bool in[150010];
void add(int x,int y,int z){
tot++;
edge[tot].to = y;
edge[tot].next = fir[x];
edge[tot].w = z;
fir[x] = tot;
}
void spfa(){
queue < int > q;
while(!q.empty())q.pop();
memset(in,false,sizeof(in));
memset(tim,0,sizeof(tim));
memset(dis,-63,sizeof(dis));
dis[minn] = 0;in[minn] = true;tim[minn] = 1;q.push(minn);
while(!q.empty()){
int x = q.front();q.pop();
in[x] = false;
for(int i = fir[x];i != -1;i = edge[i].next){
if(dis[edge[i].to] < dis[x] + edge[i].w){
dis[edge[i].to] = dis[x] + edge[i].w;
if(!in[edge[i].to]){
tim[edge[i].to]++;
if(tim[edge[i].to] > maxx)return;
in[edge[i].to] = true;
q.push(edge[i].to);
}
}
}
}
return;
}
int main(){
scanf("%d",&n);
memset(fir,-1,sizeof(fir));
memset(edge,0,sizeof(edge));
maxx = -1;minn = inf;
tot = 0;
for(int i = 1;i <= n;i++){
long long x,y,z;
scanf("%lld%lld%lld",&x,&y,&z);
if(x > y)swap(x,y);
add(x - 1,y,z);
maxx = max(maxx,y);
minn = min(minn,x - 1);
}
for(int i = minn;i <= maxx;i++){
add(i,i + 1,0);
add(i + 1,i,-1);
}
spfa();
printf("%d
",dis[maxx]);
return 0;
}