跳到主要内容

差分约束

差分约束是用来解决经一系列不等式限制后求可行解或最大值最小值的问题。

例如给下列一组这样的式子,求一组可行解 xixj+ck其中xixj是自变量,ck是常数x_i≤x_j+c_k其中x_i、x_j是自变量,c_k是常数 差分约束求不等式的可行解:

  • 源点需要满足的条件:从源点出发,一定可以走到所有的边,因为如果有边没被考虑到,相当于有些条件没考虑到。(不一定需要走到所有的点,如果一个点是孤立的,说明这个点没有任何限制;如果从一个点可以走到人以其他点,则一定可以走到所有边)。

步骤:

  1. 先将每个不等式xixj+ckx_i≤x_j+c_k转化为一条从xjx_j指向xix_i且长度为ckc_k的边
    1. xi<=xj+ck等价于dist[i]<=dist[j]+ckx_i<=x_j+c_k等价于dist[i]<=dist[j]+c_k
  2. 找一个超级原点,使得该源点可以遍历到所有边
  3. 从源点求一遍单源最短路
    1. 结果1:如果存在负环,则原不等式组一定无解

因为如果有负环,xi<=xi+cix_i<=x_i+\sum c_i,而ci<0\sum c_i<0矛盾

  1. 结果2:如果没有负环,则dist[i]就是原不等式组的一个可行解

如何求最大值或者最小值,这里最值指的是每个变量的最值: 结论:

  • 如果求的是最小值,则应该求最长路
  • 如果求的是最大值,则应该求最短路

问题:如何转化xicx_i≤c,其中c是一个常数这类不等式

  • 建立一个超级源点0,然后建立0>i0->i长度为cc的边即可

以求xix_i的最大值为例: 求所有从xix_i出发,可以构成多条如下不等式链xi<=xj+c1<=xk+c2+c1<=...<=x0+c1+c2+...x_i<=x_j+c_1<=x_k+c_2+c_1<=...<=x_0+c_1+c_2+... 所计算出的上界,最终xix_i的最大值等于所有上界的最小值:

例如: x1<=5 x1<=6 x1<=7 则x1的最大值为5

我们可以把上述问题转化为图论问题,即求dist[i]的最小值,即最短路问题 同理求xix_i的最小值可以转化为求dist[i]的最大值,即最长路问题

AcWing 1169. 糖果

题目

https://www.acwing.com/problem/content/1171/ 幼儿园里有 N 个小朋友,老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。 但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候, 老师需要满足小朋友们的 K 个要求。 幼儿园的糖果总是有限的,老师想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。 输入格式: 输入的第一行是两个整数 N,K。 接下来 K 行,表示分配糖果时需要满足的关系,每行 3 个数字 X,A,B。

  • 如果 X=1.表示第 A 个小朋友分到的糖果必须和第 B 个小朋友分到的糖果一样多。
  • 如果 X=2,表示第 A 个小朋友分到的糖果必须少于第 B 个小朋友分到的糖果。
  • 如果 X=3,表示第 A 个小朋友分到的糖果必须不少于第 B 个小朋友分到的糖果。
  • 如果 X=4,表示第 A 个小朋友分到的糖果必须多于第 B 个小朋友分到的糖果。
  • 如果 X=5,表示第 A 个小朋友分到的糖果必须不多于第 B 个小朋友分到的糖果。

小朋友编号从 1 到 N。

思路

  1. A=B 等价于 A>=B B>=A
  2. A<B 等价于 B>=A+1
  3. A>=B等价于 A>=B
  4. A>B等价于 A>=B+1
  5. B>=A等价于 B>=A

本题还有一个隐藏条件X>=1X>=1 题目要求糖果的最小值,可以转化为一个求最长路的问题

代码

#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N=100010,M=300010;
int n,m;
int h[N],e[M],ne[M],w[M],idx;
LL dist[N];
int q[N],cnt[N];
bool st[N];

void add(int a,int b,int c)
{
w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

bool spfa()
{
int hh=0,tt=1;
memset(dist,-0x3f,sizeof dist);
dist[0]=0,q[0]=0,st[0]=true;
while(hh!=tt)
{
int t=q[--tt];
st[t]=false;

for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dist[j]<dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n+1) return false;
if(!st[j])
{
q[tt++]=j;
st[j]=true;
}
}
}
}
return true;
}

int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--)
{
int x,a,b;
cin>>x>>a>>b;
if(x==1) add(b,a,0),add(a,b,0);
else if(x==2) add(a,b,1);
else if(x==3) add(b,a,0);
else if(x==4) add(b,a,1);
else add(a,b,0);
}

for(int i=1;i<=n;i++) add(0,i,1);
if(!spfa()) cout<<"-1"<<endl;
else
{
LL res=0;
for(int i=1;i<=n;i++) res+=dist[i];
cout<<res<<endl;
}
return 0;
}

AcWing 362. 区间

题目

https://www.acwing.com/problem/content/364/ 给定 n 个区间 [ai,bi]n 个整数 ci。 你需要构造一个整数集合 Z,使得 ∀i∈[1,n]Z 中满足 ai≤x≤bi 的整数 x 不少于 ci 个。 求这样的整数集合 Z 最少包含多少个数。

思路

这里可以使用前缀和的思想求解,因为前缀和中S[0]=0,所有这里将ai,bi所在的区间范围加上一个1,区间范围变成了[1, 50001],这样并不影响最终的结果。 s[i]表示:1-i中被选出数的个数,最终要求的是S[50001]的最小值,所以要求最长路 约束关系:

  • Si>=Si1,1<=i<=50001S_i>=S_{i-1},1<=i<=50001
  • SiSi1<=1等价于Si1>=Si1S_i-S_{i-1}<=1 等价于 S_{i-1}>=S_i-1
  • 区间【a,b】中至少有c个数:SbSa1>=c等价于Sb>=Sa1+cS_b -S_{a-1}>=c 等价于 S_b>=S_{a-1}+c

还需要验证从源点出发能否走到所有边,根据题意,可以从0走到50001.

代码

#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N=50010,M=150010;
int n;
int h[N],e[M],ne[M],w[M],idx;
int dist[N];
int q[N];
bool st[N];

void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

void spfa()
{
int hh=0,tt=1;
memset(dist,-0x3f,sizeof dist);
dist[0]=0,q[0]=0,st[0]=true;
while(hh!=tt)
{
int t=q[hh++];
if(hh==N) hh=0;
st[t]=false;

for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dist[j]<dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
if(!st[j])
{
q[tt++]=j;
if(tt==N) tt=0;
st[j]=true;
}
}
}
}
}


int main()
{
cin>>n;
memset(h,-1,sizeof h);
for(int i=1;i<=50001;i++)
{
add(i-1,i,0);
add(i,i-1,-1);
}

while(n--)
{
int a,b,c;
cin>>a>>b>>c;
a++,b++;
add(a-1,b,c);
}
spfa();
cout<<dist[50001];
return 0;
}