差分约束
1. 概念
- 如果一个系统由
n
个变量和m
个约束条件组成,形成m
个形如ai − aj ≤ k 的不等式(i,j∈[1,n], k为常数),则称其为差分约束系统。
2.引例
例如n=4,m=5
,有如下五个不等式:
-
x1 - x0 (le) 2
-
x2 - x0 (le) 7
-
x3 - x0 (le) 8
-
x2 - x1 (le) 3
-
x3 - x2 (le) 2
很容易化简出三个不等式:
x3 - x0 (le) 8
x3 - x0 (le) 9
x3 - x0 (le) 7
则 x3 - x0 最大为7
。
令x3 = d[v], x0 = d[u]不等式右边的值为w(u,v) 表示u
到v
的距离,然后对上面三个不等式移项可得:
-
d[v] ≤ d[u] + w(u,v)
-
对上面的式子大家是不是很熟悉,跟我们求最短路的松弛很相像!
-
对上面的关系,如果x1−x0≤2,则我们就建一条x0→x1权值为
2
的有向边,我们用下图来表示:
如果我们想求xi - xj 的最大值,只需求出xi - xj 的最短路,因为他们之间有多个 小于等于 的限制条件,我们必须全部满足,所以求的是最短路.
3. 问题解的存在性
1.当有正权回路时,无最长路. 当有负权回路时,无最短路.
2.两点间无限制条件,即不可达.
4.不等式转化
x - y (ge) w -----> y - x (le) w
x - y < w -----> x - y (le) w + 1 (x, y为整数)
x - y = w -------> x - y (le) w 且 x - y (ge) w
例题
poj1716 Integer Intervals
Description
- 区间
[a,b], a,表示包含
a,b连续整数的集合,给出若干个类似的区间集合,我们从每个集合中至少挑出
2个元素组成一个新的集合,求满足条件新集合最小元素个数。
Input
- 第一行有一个整数
n
1≤n≤10000; - 接下来
n
行,包含两个整数a,b
(0≤a<b≤100000),表示区间[a,b]
。
Output
- 输出满足条件集合的最少元素个数。
Sample Input
4
3 6
2 4
0 2
4 7
Sample Output
4
难点是怎样把这道题和差分约束联系起来
令sum[x]
为区间集合[0,x]
中的被选中的元素个数。
设某个集合 [x , y]
因为要保证这个集合内至少选两个, 于是得到不等式
sum[ y ] - sum[ x-1 ] (ge) 2 ----------> 建边:Insert(x-1,y,2)
每个元素只有选或不选两种情况,即对于单元素的集合,选的最少为0, 最多为1
sum[ i + 1 ] − sum[ i ] (ge) 0 ---------> 建边:Insert(i,i+1,0)
sum[ i ] − sum[ i + 1 ] (ge) −1 -----------> 建边:Insert(i+1,i,-1)
(这是移项转化来的)
#include <bits/stdc++.h>
const int maxn=1e4+5,Inf=0x3f3f3f3f;
using namespace std;
struct node{
int to, dis, next;
} e[maxn*3];
int dis[maxn], vis[maxn], head[maxn], cnt[maxn];
int n, len;
void Insert(int u, int v, int w){
e[++len].to = v;
e[len].dis = w;
e[len].next = head[u];
head[u] = len;
}
void spfa(int s, int t){//以s为起点, t条边
for(int i=0; i<=t; i++) dis[i] = -Inf, vis[i] = 0;//初始化
queue<int> q;
vis[s] = 1;
dis[s] = 0;
q.push(s);//起点入队
memset(cnt, 0, sizeof(cnt));//cnt[i]表示节点i的进队次数,判环用
cnt[s]++;//s入队次数加一
while(!q.empty()){
int u = q.front();//取出一点
q.pop();
vis[u]=0;
for(int i=head[u]; i; i=e[i].next){
int v=e[i].to, w=e[i].dis;
if(dis[v] < dis[u]+w){//松弛
dis[v] = dis[u] + w;
if(!vis[v]){
vis[v] = 1;
q.push(v);
cnt[v]++;
if(cnt[v] >= t) return;//说明有正环
}
}
}
}
}
int main(){
while(~scanf("%d", &n)){
len = 0;
memset(head, 0, sizeof(head));//多组数据,注意初始化
int Max=0;//记录区间右边界的最大值
for(int i=1; i<=n; i++){
int a, b; scanf("%d%d", &a, &b);
Insert(a, b+1, 2);//注意我们这里把a, b都向右平移了一位,要不然下面处理隐含条件时插边会有负数
Max = max(Max, b+1);//记录右边界的最大值
}
for(int i=0; i<=Max; i++){//处理隐含条件
Insert(i, i+1, 0);
Insert(i+1, i, -1);
}
spfa(0, Max);//0相当于-1
printf("%d
", dis[Max]);
}
return 0;
}