图论进阶——差分约束系统

前置知识

最短路算法(spfa)

例题

给定一组不等式
{ x a 1 − x a 2 ≤ b 1     x a 3 − x a 4 ≤ b 2     . . . . . .       x a n − 1 − x a n ≤ b n / 2 \left\{ \begin{aligned} x_{a_1}-x_{a_2}\le b_1\space \space\space\\ x_{a_3}-x_{a_4}\le b_2\space \space\space\\ ......\qquad\space \space\space\space \space\\ x_{a_{n-1}}-x_{a_n}\le b_{n/2} \end{aligned} \right. xa1xa2b1   xa3xa4b2   ......     xan1xanbn/2
观察这些式子,好像方法不是很明确。

然后前人经过研究,发现这个问题的解法和最短路有关。

在讲各种最短路方法时,有一点是共同的,那就是

if(d[v]>d[u]+w[e])
  d[v]=d[u]+w[e];

这个在最短路算法中叫做松弛操作,其中 v v v有向边 ( u , v ) (u,v) (u,v)终点

显然,当最短路求出后,所有的 d [ v ] d[v] d[v]都会小于等于 d [ u ] + w [ e ] d[u]+w[e] d[u]+w[e](大于的都会被松弛掉)

显然由 d [ v ] ≤ d [ u ] + w [ e ] d[v]\le d[u]+w[e] d[v]d[u]+w[e]可推出 d [ v ] − d [ u ] ≤ w [ e ] d[v]-d[u]\le w[e] d[v]d[u]w[e]

对于不等式 x a i − x a j ≤ b 1 x_{a_i}-x_{a_j}\le b_1 xaixajb1,我们从 a j a_j aj a i a_i ai连权值为 b 1 b_1 b1有向边,那么我们有 d [ a i ] − d [ a j ] ≤ b 1 d[a_i]-d[a_j] \le b_1 d[ai]d[aj]b1
将所有的不等式执行该操作后, d [ a 1 ] , d [ a 2 ] , . . . , d [ a n ] d[a_1],d[a_2],...,d[a_n] d[a1],d[a2],...,d[an]即为该不等式的一组解 { x 1 , x 2 , . . . , x n } \{ x_1,x_2,...,x_n\} {x1,x2,...,xn}

对于不等式无解的情况,就是该图无最短路的情况,只有负环的出现会造成这种局面

而我们的 s p f a spfa spfa可以轻松胜任找负环的工作。

注意

上文给出的是基于最短路的差分约束系统,用最长路可以通过 d [ v ] ≥ d [ u ] + w [ e ] d[v]≥d[u]+w[e] d[v]d[u]+w[e]来实现差分约束系统,方法同上。

Code(bfs+最长路)

SCOI2011糖果

#include<bits/stdc++.h>
#define int long long
using namespace std;
int first[1000005],nxt[1000005],to[1000005],w[1000005],tot=0;
int inq[100005],dis[100005],cnt[100005];
void Add(int x,int y,int z){
	nxt[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}
inline int Read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')  f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline void Write(int x){
	if(x<0){
		putchar('-');
		x=-x;
	}
	if(x>9){
		Write(x/10);
	}
	putchar(x%10+'0');
}
bool spfa(int n){
	queue<int> q;
	q.push(0);
	inq[0]=1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		if(cnt[u]==n-1)  return false;
		cnt[u]++;
		inq[u]=0;
		for(int e=first[u];e;e=nxt[e]){
			int v=to[e];
			if(dis[v]<dis[u]+w[e]){
				dis[v]=dis[u]+w[e];
				if(!inq[v]){
					inq[v]=1;
					q.push(v);
				}
			}
		}
	}
	return true;
}
signed main(){
	int n=Read(),m=Read();
	for(int i=1;i<=m;i++){
		int x=Read(),a=Read(),b=Read();
		if(x==1){
			Add(a,b,0);
			Add(b,a,0);
		}
		if(x==2){
			if(a==b){
				cout<<"-1\n";
				return 0;
			}
			Add(a,b,1);
		}
		if(x==3)  Add(b,a,0);
		if(x==4){
			if(a==b){
				cout<<"-1\n";
				return 0;
			}
			Add(b,a,1);
		}
		if(x==5)  Add(a,b,0);
	}
	for(int i=n;i>=1;i--){            //加边顺序优化
		Add(0,i,1);
	}
	if(!spfa(n)){
		cout<<"-1\n";
		return 0;
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		ans+=dis[i];
	}
	cout<<ans<<endl;
}

UPD:2019.10.13

对于找负环来说,实测 d f s dfs dfs版spfa比 b f s bfs bfs快100倍左右。
例题:小k的农场

Code(dfs+最短路)
#include<bits/stdc++.h>
#define inf 2147483647
using namespace std;
inline int Read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')  f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline void Write(int x){
	if(x<0){
		putchar('-');
		x=-x;
	}
	if(x>9){
		Write(x/10);
	}
	putchar(x%10+'0');
}
int first[200005],nxt[200005],to[200005],w[200005],tot=0;
void Add(int x,int y,int z){
	nxt[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}
int n,m,d[200005],inq[200005],cnt[200005];
bool spfa(int u){
	inq[u]=1;
	for(int e=first[u];e;e=nxt[e]){
		int v=to[e];
		if(d[v]>d[u]+w[e]){
			d[v]=d[u]+w[e];
			if(inq[v])  return false;
			if(!spfa(v))  return false;
		}
	}
	inq[u]=0;
	return true;
}
signed main(){
	n=Read(),m=Read();
	int opt,a,b,c;
	for(int i=1;i<=n;i++){
		Add(0,i,1);
	}
	for(int i=1;i<=m;i++){
		opt=Read();
		if(opt==3){
			a=Read(),b=Read();
			Add(a,b,0);
			Add(b,a,0);
		}
		if(opt==2){
			a=Read(),b=Read(),c=Read();
			Add(b,a,c);
		}
		if(opt==1){
			a=Read(),b=Read(),c=Read();
			Add(a,b,-c);
		}
	}
	memset(d,10,sizeof(d));
	d[0]=0;
	if(spfa(0))  cout<<"Yes\n";
	else  cout<<"No\n";
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值