差分约束系统

差分约束系统是如下一组不等式:

\tiny f(x)=\left\{ \begin{aligned} x_{1} - x_{1'} <= y_{1} \\ x_{2} - x_{2'} <= y_{2} \\ ............\\ x_{n} - x_{n'} <= y_{n} \\ \end{aligned} \right.

对于一系列不等式关系,可以转换为差分约束系统来解决。

我们来看一条不等式:\tiny x_{1}-x_{1'} <= y_{1},移项后我们得到\tiny x_{1}<= x_{1'} + y_{1},观察这个式子,观察这个式子,\tiny x_{1}\tiny x_{1'}同型,读者是否联想到了最短路问题中的松弛操作呢?\tiny dist[u] = dist[v] + w_{v,u}。于是我们可以借助这些不等式构建一个图,即若有\tiny x_{i} = x_{i'}+y_{i},增加 \tiny i' 到 \tiny i 一条权值为 \tiny y_{i} 的边。

举个例子:当我们有如下约束时

\tiny \dpi{200} \tiny f(x)=\left\{ \begin{aligned} x_{1} - x_{2} <= 2 \\ x_{2} - x_{3} <= -3 \\ x_{3} - x_{1} <= 4 \\ \end{aligned} \right.

我们按照上述构图规则可以得到如下的有向图:

虽然我们是想通过最短路的方式解决这个问题的,但是我们会发现,题目只给了一系列不等式约束,但是我们并不知道源点在哪里呀?那怎么求最短路嘞?其实没所谓的啦,事实上从哪个点出发都行,因为只要是按照最短路的方式跑,一定会得到满足不等式约束的。不过问题是,我们根据题目所给出的约束不等式所构建的图可能不是联通的,这就会导致最终结果会有INF。所以为了避免这种情况,所以我们会人为加入一个超级源点0,使得它到所有点都有一条权值的0的有向边。

相当于是给原差分系统加入了一下的一系列系列约束:

\tiny f(x)=\left\{ \begin{aligned} x_{1} - x_{0} <= 0 \\ x_{2} - x_{0} <= 0 \\ x_{3} - x_{0} <= 0 \\ \end{aligned} \right.

于是增加了超级源点之后,原有向图变为了:

当我们设dist[0]=0的时候,根据最短路的性质,并且由于从0出发可以到达每一个点,所以以0为起点的所有最短路的长度均满足dist[i]<=0。不过有时候题目会要求我们给出非负解,不过这并不是难事,事实上,在差分系统中,给所有的未知量\tiny x_{i}加上相同的数,仍然会符合约束。因此我们完全可以找到最小的数num,如果num<0,则给所有的未知量都加上-num。

题目往往还会要求我们给出符合\tiny x_{i}<=w的最大解(即所有的\tiny x_{i}都取各自可以取到的最大值)。可以证明,当我们设dist[0]=w时,求出的就是满足\tiny x_{i}<=w的一组解。

有时候题目还有要求我们给出符合\tiny x_{i}>=w的最小解(所有的\tiny x_{i}都取各自可以取到的最小值)。

洛谷 P5960 【模板】差分约束算法

提交2.63k

通过1.28k

时间限制1.00s

内存限制125.00MB

题目描述

给出一组包含 m 个不等式,有 n 个未知数的形如:

\begin{cases} x_{c_1}-x_{c'_1}\leq y_1 \\x_{c_2}-x_{c'_2} \leq y_2 \\ \cdots\\ x_{c_m} - x_{c'_m}\leq y_m\end{cases}⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧​xc1​​−xc1′​​≤y1​xc2​​−xc2′​​≤y2​⋯xcm​​−xcm′​​≤ym​​

的不等式组,求任意一组满足这个不等式组的解。

输入格式

第一行为两个正整数 n,m,代表未知数的数量和不等式的数量。

接下来 m 行,每行包含三个整数 c,c',y,代表一个不等式 x_c-x_{c'}\leq y_{c}

输出格式

一行,n 个数,表示  x_1,x_2\cdots x_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;
} 

值得注意的是,在一般问题中,可能不会有明显的不等关系,需要我们自己做转化,如下:

\tiny x_{1}-x_2 >= y ,两边同乘-1转换为  \tiny x_{2}-x_1 <= -y

\tiny x_{1}-x_2=y 可以取 >= 与 <=的交 则转变为  \tiny x_1-x_2<=y 且  \tiny x_2-x_1<=y

\tiny x_1-x_2<y 在整数的情形下,可以转化为 \tiny x_1-x_2<=y-1

\tiny x_1-x_2>y,两边同乘-1化为 \tiny x_2-x_1<-y,在整数的条件下,化为\tiny x_2-x_1<=-y-1

 

洛谷 P1250 种树

提交13.04k

通过5.34k

时间限制1.00s

内存限制125.00MB

题目背景

一条街的一边有几座房子,因为环保原因居民想要在路边种些树。

题目描述

路边的地区被分割成块,并被编号成 1, 2, \ldots,n。每个部分为一个单位尺寸大小并最多可种一棵树。

每个居民都想在门前种些树,并指定了三个号码 b,e,t。这三个数表示该居民想在地区 b 和 e 之间(包括 b 和 e)种至少 t 棵树。居民们想种树的各自区域可以交叉。你的任务是求出能满足所有要求的最少的树的数量。

输入格式

输入的第一行是一个整数,代表区域的个数 n。

输入的第二行是一个整数,代表房子个数 h。

第 3 到第 (h + 2)行,每行三个整数,第 (i + 2)行的整数依次为b_i, e_i, t_i,代表第 i 个居民想在 b_i​ 和 e_i​ 之间种至少 t_i​ 棵树。

输出格式

输出一行一个整数,代表最少的树木个数。

输入输出样例

输入 #1复制

9
4
1 4 2
4 6 2
8 9 2
3 5 2

输出 #1复制

5

思路:这题的意思是给出某个区间内的数量约束,当我们使用前缀和后,就可以把区间数量约束转化为不等式约束了。即在地区 b 和 e 之间(包括 b 和 e)种至少 t 棵树可以等效表示为 \tiny S[e]-S[b-1]>=t,根据我们上面所提到的,我们可以将这个约束转化为 \tiny S[b-1]-S[e]<=-t ,同时注意到每个部分为一个单位尺寸大小并最多可种一棵树,用不等式约束可以表示为\tiny 0<= S_{i+1}-S_i<=1,同样转化为\tiny S_{i+1}-S{i}<=1\tiny S_i-S_{i+1}<=0。由于前缀和数组的构成,最终所求的答案就是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;
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值