差分约束
NOIp%你怎么考这啊这我怎么没学啊赶紧补课
我们对于一组形如 (x_ile x_j+c_k) 的不等式组,可以使用 差分约束 求出不等式组的一组 可行解 。
观察 (x_ile x_j+c_k) 的形式,我们会联想到 (dis_xle dis_y+w_i) ,这是最短路的判定式移了个项。同时我们可以知道,我们做完最短路之后,只要图中没有负环,所有的 (dis_x) 都满足上面的式子。
这意味着我们可以把不等式组的问题转化成一个差分约束的问题。我们将每一个不等式都抽象成边,就将其转换成了一个图论最短/长路问题。需要注意的是,我们跑最短路的时,必须 找到一个源点能够遍历到所有的边,这样才能考虑到所有的限制条件。
可行解
一般的求可行解的步骤:
- 先把每个不等式 (x_ile x_j+c_k) 转化成一条 (x_j) 连向 (x_i) 的权值为 (c_k) 的有向边。
- 找一个源点,使得该点能够遍历到所有的边。
- 从源点求一遍单元最短路。
单源最短路有另外一个问题:负环。
我们来把负环的情况放在不等式中考虑:
假设环的形态如下:
我们根据图中得到的不等关系进行放缩:
那么 (x_2le x_1+c_1le x_k+c_1+c_kle cdots le x_2+c_1+c_k+c_{k-1}+cdots c_4+c_3+c_2)
由于这是个负环,所以 (sum_{i=1}^kc_i < 0),设这个式子结果为 (p),则 (x_2le x_2+p, p<0)。显然不成立。
所以,当图中出现 负环的时候,证明不等式组无解。
求变量最值
结论:
- 如果求最小值,则化成 (x_ige x_j+c) 求最长路。
- 如果求最大值,则化成 (x_ile x_j+c) 求最短路。
求最值的时候一定会有一个条件类似 (x_ile k),(k) 为常数。如果没有这类大于或小于常数的限制,那么对于任意一组可行解 ({x_1,x_2,dots,x_k}) 以及任意一个 (din R) ,({x_1+d,x_2+d,dots x_k+d}) 都是一组可行解,没有最值一说。
如何转化 (x_ile k) 这类的不等式呢?
我们可以先建立一个超级源点 (0),然后建立 (0 o i) 的长度为 (k) 一条边即可。
为什么我们这样做就可以求出最大/最小值?
我们以求 (x_i) 的最大值为例子说明:
从上面负环的例子可以知道,我们从 (0) 开始求到 (x_i) 的最短路,其实是求出一个有关变量 (x_i) 的不等式链,不等式链最后都能转化为 (x_ile sum_{i=1}^kc_i)。那么所有的不等式链的 (sum c_i) 的最小值就是 (x_i) 的上界。
同理,求最小值就是在所有的下界中找最大值。
例题
『SCOI2011』糖果
简化题意:
有 (n) 个变量,有 (k) 组关系,关系有以下 (5) 种:
- (x_i=x_j)
- (x_i<x_j)
- (x_ige x_j)
- (x_i>x_j)
- (x_ile x_j)
已知所有的 (xge 1) ,求最小和。
解析
来看一下这五种关系:
- (x_i=x_jRightarrow x_ige x_j, x_jge x_i)。
- (x_i<x_jRightarrow x_jge x_i+1)
- (x_ige x_j) 不需要转化
- (x_i>x_jRightarrow x_ige x_j+1)
- (x_ile x_jRightarrow x_jge x_i)。
注意隐含条件: (forall xge 1)。
那么这就是所有我们需要的用来建立不等式组的不等式。直接建图跑最长路即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200010,M=400010;
ll n,m;
ll head[M],nxt[M],ver[M],edg[M],tot=0;
ll dis[N],cnt[N];
queue<int> q;
bool vis[N];
void add(int x,int y,int z)
{
ver[++tot]=y;
edg[tot]=z;
nxt[tot]=head[x];
head[x]=tot;
}
bool spfa()
{
memset(dis,-0x3f,sizeof dis);
dis[0]=0,vis[0]=1;
q.push(0);
while(q.size())
{
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(dis[y]<dis[x]+edg[i])
{
dis[y]=dis[x]+edg[i];
if(!vis[y])
{
cnt[y]++;
vis[y]=1;
q.push(y);
if(cnt[y]>n) return 0;//判环
}
}
}
}
return 1;
}
int main()
{
scanf("%lld%lld",&n,&m);
bool flag=0;
for(int i=1;i<=m;i++)
{
int x,a,b;
scanf("%d%d%d",&x,&a,&b);
if(x==1) add(a,b,0),add(b,a,0);
else if(x==2) add(a,b,1),flag=(a==b?1:flag);
else if(x==3) add(b,a,0);
else if(x==4) add(b,a,1),flag=(a==b?1:flag);
else add(a,b,0);
}
if(flag==1)
{
cout<<-1;
return 0;
}
for(int i=n;i>=1;i--)
add(0,i,1);
flag=spfa();
ll ans=0;
for(int i=1;i<=n;i++)
ans+=dis[i];
if(flag==0) printf("-1");
else printf("%lld",ans);
return 0;
}
区间
给定 (n) 个区间 ([a_i,b_i]) 和 (n) 个整数 (c_i)。
你需要构造一个整数集合 (Z),使得 (forall iin [1,n]),(Z) 中满足 (a_i≤x≤b_i) 的整数 (x) 不少于 (c_i) 个。
求这样的整数集合 (Z) 最少包含多少个数。
解析
满脑子都是贪心.jpg
看着和我们上面讲到的模型没有一点关系是吧?
设 (S_i) 是 ([1,i]) 中被选中的数的个数。
我们要求的是 (S_{50001}) 的最小值。
此时我们试着分析 (S_i) 的规律,发现:
-
(S_ige S_{i-1}, iin [1,50001])
-
(S_i-S_{i-1}le 1Rightarrow S_{i-1}+1ge S_i,iin [1,50001])
-
对于每一个 ([a_i,b_i],S_{b_i}-S_{a_i-1}ge c_i)。
现在我们想一下,是否是只要满足这几个条件方案就一定合法?
前两个条件是数字的性质,第三条件就是我们题中的限制条件,条件是够了。
然后我们需要找是否有一个源点能遍历到所有的边。由于第一个条件连接所有点成了一条链,所以我们能遍历到所有的点,自然也能摸到所有的边。
所以建边最长路即可。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int head[N],ver[N<<1],nxt[N<<1],edg[N<<1],tot=0;
void add(int x,int y,int w)
{
ver[++tot]=y; edg[tot]=w; nxt[tot]=head[x]; head[x]=tot;
}
int read()
{
int x=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9') w*=(ch=='-'?-1:1),ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return x*w;
}
int dis[N];
bool vis[N];
void spfa()
{
memset(vis,0,sizeof vis);
memset(dis,-0x3f,sizeof dis);
queue<int> q;
dis[0]=0; vis[0]=1;
q.push(0);
while(q.size())
{
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(dis[y]<dis[x]+edg[i])
{
dis[y]=dis[x]+edg[i];
if(!vis[y])
{
vis[y]=1;
q.push(y);
}
}
}
}
}
int main()
{
n=read();
for(int i=1;i<=50001;i++)
{
add(i,i-1,-1);
add(i-1,i,0);
}
for(int i=1;i<=n;i++)
{
int a,b,c;
a=read();b=read();c=read();
add(a-1,b,c);
}
spfa();
printf("%d",dis[50001]);
return 0;
}
『USACO2005Dec. G』Layout
不复述题面了自己去看。
解析
设 (x_i) 是第 (i) 头奶牛到第一头奶牛的距离。
题面明确了两种条件:
- (x_a-x_bge L)
- (x_a-x_ble R)
同时,所有的 (x_ige 0,x_ile x_i-1)。
我们要求 (x_n) 的最大值,显然要跑最短路。
那么整理一下不等式:
我们发现连通性似乎有问题,所以我们再加一个不等式: (x_ige 0)。这相当于建立了一个超级源点,向每个点都连了一条边。但是我们没有必要把它显性建立出来,只需要在一开始把所有点都入队即可。
现在我们要想如何判定 (1) 号点与 (n) 号点距离能否无限大。
此时我们可以令 (x_1) 为一个定值:让 (x_1=0),然后去找 (x_n) 是否受影响。
具体做法:我们只需要从 (x_1) 向 (x_n) 跑一遍最短路,判定是否能达到无限大就行了。
#include <bits/stdc++.h>
using namespace std;
const int N=1e4+10,M=1e6+10;
int head[N],ver[M],nxt[M],edg[M],tot=0;
void add(int x,int y,int w)
{
ver[++tot]=y; edg[tot]=w; nxt[tot]=head[x]; head[x]=tot;
}
int n,ml,mr;
int read()
{
int x=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9') w*=(ch=='-'?-1:1),ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return x*w;
}
int dis[N],cnt[N];
bool vis[N];
bool spfa(int k)
{
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
memset(cnt,0,sizeof cnt);
queue<int> q;
for(int i=1;i<=k;i++)
{
q.push(i);
vis[i]=1; dis[i]=0;
cnt[i]++;
}
while(q.size())
{
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];
if(dis[y]>dis[x]+edg[i])
{
dis[y]=dis[x]+edg[i];
if(!vis[y])
{
vis[y]=1;
q.push(y);
if(++cnt[y]>n) return 0;
}
}
}
}
return 1;
}
int main()
{
n=read(),ml=read(),mr=read();
for(int i=1;i<n;i++) add(i+1,i,0);
for(int i=1;i<=ml;i++)
{
int a,b,c;
a=read(); b=read(); c=read();
if(b<a) swap(a,b);
add(a,b,c);
}
for(int i=1;i<=mr;i++)
{
int a,b,c;
a=read(); b=read(); c=read();
if(b<a) swap(a,b);
add(b,a,-c);
}
if(!spfa(n))
{
printf("-1");
return 0;
}
spfa(1);
if(dis[n]==0x3f3f3f3f) printf("-2");
else printf("%d",dis[n]);
return 0;
}