差分约束系统是如下一组不等式:
对于一系列不等式关系,可以转换为差分约束系统来解决。
我们来看一条不等式:,移项后我们得到,观察这个式子,观察这个式子,与同型,读者是否联想到了最短路问题中的松弛操作呢?。于是我们可以借助这些不等式构建一个图,即若有,增加 到 一条权值为 的边。
举个例子:当我们有如下约束时
我们按照上述构图规则可以得到如下的有向图:
虽然我们是想通过最短路的方式解决这个问题的,但是我们会发现,题目只给了一系列不等式约束,但是我们并不知道源点在哪里呀?那怎么求最短路嘞?其实没所谓的啦,事实上从哪个点出发都行,因为只要是按照最短路的方式跑,一定会得到满足不等式约束的。不过问题是,我们根据题目所给出的约束不等式所构建的图可能不是联通的,这就会导致最终结果会有INF。所以为了避免这种情况,所以我们会人为加入一个超级源点0,使得它到所有点都有一条权值的0的有向边。
相当于是给原差分系统加入了一下的一系列系列约束:
于是增加了超级源点之后,原有向图变为了:
当我们设dist[0]=0的时候,根据最短路的性质,并且由于从0出发可以到达每一个点,所以以0为起点的所有最短路的长度均满足dist[i]<=0。不过有时候题目会要求我们给出非负解,不过这并不是难事,事实上,在差分系统中,给所有的未知量加上相同的数,仍然会符合约束。因此我们完全可以找到最小的数num,如果num<0,则给所有的未知量都加上-num。
题目往往还会要求我们给出符合<=w的最大解(即所有的都取各自可以取到的最大值)。可以证明,当我们设dist[0]=w时,求出的就是满足<=w的一组解。
有时候题目还有要求我们给出符合>=w的最小解(所有的都取各自可以取到的最小值)。
洛谷 P5960 【模板】差分约束算法
提交2.63k
通过1.28k
时间限制1.00s
内存限制125.00MB
题目描述
给出一组包含 m 个不等式,有 n 个未知数的形如:
的不等式组,求任意一组满足这个不等式组的解。
输入格式
第一行为两个正整数 n,m,代表未知数的数量和不等式的数量。
接下来 m 行,每行包含三个整数 c,c',y,代表一个不等式 。
输出格式
一行,n 个数,表示 的一组可行解,如果有多组解,请输出任意一组,无解请输出 NO
。
输入输出样例
输入 #1复制
3 3
1 2 3
2 3 -2
1 3 1
输出 #1复制
5 3 5
思路:模板题,直接套即可。需要注意的是,由于我们增加了一个超级源点,所以邻接表数组需要开大一倍,增加从0到i的n条有向边。另外,约束无解的条件是最短路存在负环,所以需要一个判负环的方法。在SPFA中,只要一个点进入队列n次,则必定存在负环,用于判断是否有解。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MaxN = 5010;
// x1 - x2 <= y1 = > x1 <= x2 + y1 dist[u] = dist[v] + w-v,u
struct Edge{
int to,w,next;
};
Edge edge[MaxN<<1];
bool inque[MaxN];
int dist[MaxN],head[MaxN],total[MaxN<<1],cnt;
queue<int> que;
int read()//快读
{
int ans = 0, sgn = 1;
char c = getchar();
while (!isdigit(c))
{
if (c == '-')
sgn *= -1;
c = getchar();
}
while (isdigit(c))
{
ans = ans * 10 + c - '0';
c = getchar();
}
return ans * sgn;
}
void add(int from, int to, int w){
edge[++cnt].to = to;
edge[cnt].w = w;
edge[cnt].next = head[from];
head[from] = cnt;
}
bool SPFA(int S,int n){
memset(dist,0x3f,sizeof(dist));
int e,to;
inque[S] = true;
dist[S] = 0;
total[S] = 1;
que.push(S);
while( !que.empty() ){
S = que.front();
if(total[S] == n)return false;//存在负环
que.pop();
inque[S] = false;
for(e=head[S]; e!=0; e=edge[e].next){
to = edge[e].to;
if(dist[to] > dist[S] + edge[e].w){
dist[to] = dist[S] + edge[e].w;
if( !inque[to] ){
inque[to] = true;
que.push(to);
total[to]++;
}
}
}
}
return true;
}
int main(){
int u,v,w,n,m,i;
n = read(); m = read();
for(i=1; i<=n; ++i)add(0,i,0);
for(i=1; i<=m; ++i){
u = read();
v = read();
w = read();
add(v, u, w);
}
if(SPFA(0,n)){
for(i=1; i<=n; ++i){
printf("%d ",dist[i]);
}
}else{
puts("NO");
}
return 0;
}
值得注意的是,在一般问题中,可能不会有明显的不等关系,需要我们自己做转化,如下:
,两边同乘-1转换为
可以取 >= 与 <=的交 则转变为 且
在整数的情形下,可以转化为
,两边同乘-1化为 ,在整数的条件下,化为
洛谷 P1250 种树
提交13.04k
通过5.34k
时间限制1.00s
内存限制125.00MB
题目背景
一条街的一边有几座房子,因为环保原因居民想要在路边种些树。
题目描述
路边的地区被分割成块,并被编号成 。每个部分为一个单位尺寸大小并最多可种一棵树。
每个居民都想在门前种些树,并指定了三个号码 b,e,t。这三个数表示该居民想在地区 b 和 e 之间(包括 b 和 e)种至少 t 棵树。居民们想种树的各自区域可以交叉。你的任务是求出能满足所有要求的最少的树的数量。
输入格式
输入的第一行是一个整数,代表区域的个数 n。
输入的第二行是一个整数,代表房子个数 h。
第 3 到第 (h + 2)行,每行三个整数,第 (i + 2)行的整数依次为,代表第 i 个居民想在 和 之间种至少 棵树。
输出格式
输出一行一个整数,代表最少的树木个数。
输入输出样例
输入 #1复制
9
4
1 4 2
4 6 2
8 9 2
3 5 2
输出 #1复制
5
思路:这题的意思是给出某个区间内的数量约束,当我们使用前缀和后,就可以把区间数量约束转化为不等式约束了。即在地区 b 和 e 之间(包括 b 和 e)种至少 t 棵树可以等效表示为 ,根据我们上面所提到的,我们可以将这个约束转化为 ,同时注意到每个部分为一个单位尺寸大小并最多可种一棵树,用不等式约束可以表示为,同样转化为和。由于前缀和数组的构成,最终所求的答案就是S[n]。需要注意的坑是 b-1可能为0,为了保证超级源点的存在,我们可以令n+10为一个超级源点(只要不和已有的点重复,具体是哪一个点无所谓)。
本题需要注意的坑点:
1.在做保证每一个单位上最多只有一颗树的约束时,需要加入S[0]以保证对S[1]的约束,所以我们不能用0作为超级源点
2.本题的邻接数组空间,需要开 3*n+m
3.注意S[n]和S[n-1]构成约束,不要加入S[n+1]
代码:
#include<bits/stdc++.h>
using namespace std;
const int MaxN = 30010;
const int MaxH = 100010;
// x1 - x2 <= y1 = > x1 <= x2 + y1 dist[u] = dist[v] + w-v,u
struct Edge{
int to,w,next;
};
Edge edge[MaxH];
bool inque[MaxN];
int dist[MaxN],head[MaxN],total[MaxN],cnt;
queue<int> que;
int read(){ //快读
int ans = 0, sgn = 1;
char c = getchar();
while (!isdigit(c))
{
if (c == '-')
sgn *= -1;
c = getchar();
}
while (isdigit(c))
{
ans = ans * 10 + c - '0';
c = getchar();
}
return ans * sgn;
}
void add(int from, int to, int w){
edge[++cnt].to = to;
edge[cnt].w = w;
edge[cnt].next = head[from];
head[from] = cnt;
}
bool SPFA(int S,int n){
memset(dist,0x3f,sizeof(dist));
int e,to;
inque[S] = true;
dist[S] = 0;
total[S] = 1;
que.push(S);
while( !que.empty() ){
S = que.front();
if(total[S] == n)return false;//存在负环
que.pop();
inque[S] = false;
for(e=head[S]; e!=0; e=edge[e].next){
to = edge[e].to;
if(dist[to] > dist[S] + edge[e].w){
dist[to] = dist[S] + edge[e].w;
if( !inque[to] ){
inque[to] = true;
que.push(to);
total[to]++;
}
}
}
}
return true;
}
int main(){
int b,e,t,n,h,i;
n = read(); h = read();
for(i=0; i<n; ++i){
add(i, i+1, 1);
add(i+1, i, 0);
add(n+10, i, 0);
}
add(n+10,n,0);
for(i=1; i<=h; ++i){
b = read();
e = read();
t = read();
add(e, b-1, -t);
}
SPFA(n+10,n);
printf("%d\n",dist[n]-dist[0]);
return 0;
}