差分约束学习笔记

差分约束可以求出这样的不等式组的 ({x_i}) 的一组整数解

[x_{a_1}-x_{b_1} le c_1\ x_{a_2}-x_{b_2} le c_2\ vdots\ x_{a_n}-x_{b_n} le c_n\ ]

对于一个不等式可以移个项 (x_{a_i}le c_i+x_{b_i})

然后可以想到最短路松弛操作

用所有连接 (v) 的权值为 (w_i)((u_i,v)),用来更新 (dis_v)

(dis_{v}=min{dis_{u_i}+w_i})

因为 (dis_v) 是取了最小的,所以对于所有连接 (v) 的权值为 (w_i)((u_i,v)),都有:

(dis_{v}le dis_{u_i}+w_i)

所以对每一个约束 (x_{a_i}le c_i+x_{b_i}),连一条 (b_i)(a_i) 的边,权值为 (c_i)

这样跑完最短路,({dis_i}) 就能满足原来的不等式组了,而对于 ({dis_i+d}) 也是满足的

若有负环,则原不等式组无解

当然,不等式组的 "(le)" 都可以换成 "(ge)",只不过变成跑最长路了(这里要注意,很容易搞错要跑最长路还是最短路,要看你的不等式的情况)

若题中既有 "(le)" 也有 "(ge)",将 "(le)" 或 "(ge)" 的乘个 (-1) 将其统一一致


Luogu P1993 小 K 的农场

下文为表述方便,将 (a)(b) 长度为 (c) 的有向边表示为 (a stackrel{c}{longrightarrow} b)

题目描述

小 K 在 MC 里面建立很多很多的农场,总共 (n) 个,以至于他自己都忘记了每个农场中种植作物的具体数量了,他只记得一些含糊的信息(共 (m) 个),以下列三种形式描述:

  • 农场 (a) 比农场 (b) 至少多种植了 (c) 个单位的作物;

  • 农场 (a) 比农场 (b) 至多多种植了 (c) 个单位的作物;

  • 农场 (a) 与农场 (b) 种植的作物数一样多。

但是,由于小 K 的记忆有些偏差,所以他想要知道存不存在一种情况,使得农场的种植作物数量与他记忆中的所有信息吻合。

思路
信息一:(x_age x_b+cimplies x_b le x_a-c),所以连 (a stackrel{-c}{longrightarrow} b)

信息二:(x_ale x_b+c),所以连 (b stackrel{c}{longrightarrow} a)

信息三:(x_a=x_bimplies x_a le x_b,x_b le x_a),所以连 (astackrel{0}{longrightarrow} b)(bstackrel{0}{longrightarrow} a)

判断负环时,记一个 (cnt_i) 表示起点到 (i) 的最短路上有多少个点,若大于 (n) 说明这条路径走了重复的点,也就说明有负环

#include <algorithm>
#include <utility>
#include <vector>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int maxn = 5e3 + 10;
const ll inf = 1e15;
vector<pair<int,int> > edge[maxn];
int n,m,cnt[maxn];
bool vis[maxn];
ll dis[maxn];
inline bool spfa(int s) {
	fill(dis,dis+maxn,inf);
	queue<int> q;
	vis[s] = true;
	q.push(s);
	dis[s] = 0;
	cnt[s] = 1;
	for (;q.size();) {
		int now = q.front(); q.pop();
		vis[now] = false;
		for (size_t i = 0;i < edge[now].size();i++) {
			int to = edge[now][i].first,w = edge[now][i].second;
			if (dis[to] > dis[now]+w) {
				dis[to] = dis[now]+w;
				cnt[to] = cnt[now]+1;
				if (cnt[to] > n) return false;
				if (!vis[to]) {
					vis[to] = true;
					q.push(to);
				}
			}
		}
	}
	return true;
}
int main() {
	scanf("%d%d",&n,&m);
	for (int i = 1,k,u,v,w;i <= m;i++) {
		scanf("%d%d%d",&k,&u,&v);
		if (k == 1) {
			scanf("%d",&w);
			edge[u].push_back(make_pair(v,-w));
		}
		if (k == 2) {
			scanf("%d",&w);
			edge[v].push_back(make_pair(u,w));
		}
		if (k == 3) {
			edge[u].push_back(make_pair(v,0));
			edge[v].push_back(make_pair(u,0));
		}
	}
	bool ans = true;
	for (int i = 1;i <= n;i++) if (!cnt[i]) ans &= spfa(i);
	puts(ans ? "Yes" : "No");
	return 0;
}

[SCOI2011]糖果

题目描述

幼儿园里有 (n) 个小朋友,( ext{lxhgww}) 老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。同时小朋友们会提出一些如 k a b 的要求

如果 k=1, 表示第 (a) 个小朋友分到的糖果必须和第 (b) 个小朋友分到的糖果一样多;

如果 k=2, 表示第 (a) 个小朋友分到的糖果必须少于第 (b) 个小朋友分到的糖果;

如果 k=3, 表示第 (a) 个小朋友分到的糖果必须不少于第 (b) 个小朋友分到的糖果;

如果 k=4, 表示第 (a) 个小朋友分到的糖果必须多于第 (b) 个小朋友分到的糖果;

如果 k=5, 表示第 (a) 个小朋友分到的糖果必须不多于第 (b) 个小朋友分到的糖果;

思路

首先 (x_i ge 1,iin [1,n]),令 (x_0 = 0),则 (x_i ge x_0+1)

这里我们跑最长路方便一些,所有的不等式转成 "(ge)"

k=1(x_a=x_bimplies x_a ge x_b,x_b ge x_a),连 (astackrel{0}{longrightarrow} b)(bstackrel{0}{longrightarrow} a)

k=2(x_a < x_b implies x_a le x_b-1 implies x_b ge x_a+1),连 (astackrel{1}{longrightarrow} b)

k=3(x_a ge x_b),连 (bstackrel{0}{longrightarrow} a)

k=4(x_a > x_b implies x_a ge x_b+1),连 (bstackrel{1}{longrightarrow} a)

k=3(x_b ge x_a),连 (astackrel{0}{longrightarrow} b)

因为都是整数,就把 "(>)" 和 "(<)" 变成了 "(ge)" 和 "(le)"

#include <algorithm>
#include <utility>
#include <vector>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
vector<pair<int,int> > edge[maxn];
int n,k,cnt[maxn];
bool vis[maxn];
ll dis[maxn];
inline bool spfa(int s) {
	queue<int> q;
	vis[s] = true;
	q.push(s);
	dis[s] = 0;
	cnt[s] = 1;
	for (;q.size();) {
		int now = q.front(); q.pop();
		vis[now] = false;
		for (size_t i = 0;i < edge[now].size();i++) {
			int to = edge[now][i].first,w = edge[now][i].second;
			if (dis[to] < dis[now]+w) {
				dis[to] = dis[now]+w;
				cnt[to] = cnt[now]+1;
				if (cnt[to] > n+1) return false;
				if (!vis[to]) {
					vis[to] = true;
					q.push(to);
				}
			}
		}
	}
	return true;
}
int main() {
	for (scanf("%d%d",&n,&k);k--;) {
		int x,a,b;
		scanf("%d%d%d",&x,&a,&b);
		if (x == 1) {
			edge[a].push_back(make_pair(b,0));
			edge[b].push_back(make_pair(a,0));
		}
		if (x == 2) edge[a].push_back(make_pair(b,1));
		if (x == 3) edge[b].push_back(make_pair(a,0));
		if (x == 4) edge[b].push_back(make_pair(a,1));
		if (x == 5) edge[a].push_back(make_pair(b,0));
		if ((x == 2 || x == 4) && a == b) return printf("-1")*0;
	}
	for (int i = 1;i <= n;i++) edge[0].push_back(make_pair(i,1));
	bool noans = spfa(0);
	if (!noans) return printf("-1")*0;
	long long ans = 0;
	for (int i = 1;i <= n;i++) ans += dis[i];
	printf("%lld",ans);
    return 0;
}

Luogu P4926 [1007]倍杀测量者

题目描述

当一位选手 A 的分数不小于选手 B 的分数 (k)(k>0))倍时,我们称选手 A (k)倍杀了选手 B ,选手 B 被选手 A (k)倍杀

训练前有不少选手立下了诸如“我没 (k) 倍杀选手X,我就女装”,“选手Y把我 (k) 倍杀,我就女装”的 Flag。与此同时,一些选手的成绩是已知的

Patchouli 为了维持机房秩序,放宽了选手们的 Flag 限制。Patchouli 设定了一个常数 (T),立下“我没 (k) 倍杀选手X就女装”的选手只要成功 ((k-T)) 倍杀了选手 X,就不需要女装。同样的,立下“选手Y把我 (k) 倍杀我就女装”的选手只要没有成功被选手Y (k+T) 倍杀,也不需要女装

Scarlet 想要确定最大的实数 (T) 使得赛后一定有选手女装

思路

首先令 (x_0 = 1),所以已知分数的选手 A,有 (x_A = c_Ax_0implies x_A ge c_Ax_0,x_0ge frac{1}{c_A}x_A),连 (0stackrel{c_A}{longrightarrow} A)(Astackrel{frac{1}{c_A}}{longrightarrow} 0)

若要没人女装,所有的限制都应满足

对选手 B 立下 Flag 1 的选手 A 要满足 (x_A > (k-T)x_B),连 (Bstackrel{k-T}{longrightarrow} A)

对选手 B 立下 Flag 2 的选手 A 要满足 (x_B < (k+T)x_A) 也就是 (x_A > frac{1}{(k+T)}x_B),连 (Bstackrel{frac{1}{(k+T)}}{longrightarrow} A)

所以当无解时有人女装,二分答案 (T),跑乘积最长路判断。

#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
const double eps = 1e-8;
const int maxn = 1000 + 10;
typedef long long ll;
struct flags { ll o,a,b,k; } q[maxn<<1];
vector<pair<int,double> > edge[maxn];
ll n,s,t,cnt[maxn];
double dis[maxn];
bool vis[maxn];
inline bool check(double mid) {
	for (ll i = 0;i <= n;i++) {
		edge[i].clear();
		vis[i] = cnt[i] = dis[i] = 0;
	}
	for (ll i = 1;i <= s+t;i++) {
		if (q[i].o == 1) edge[q[i].b].push_back(make_pair(q[i].a,1.*q[i].k-mid));
		if (q[i].o == 2) edge[q[i].b].push_back(make_pair(q[i].a,1./(1.*q[i].k+mid)));
		if (q[i].o == 3) {
			edge[q[i].a].push_back(make_pair(0,1./q[i].k));
			edge[0].push_back(make_pair(q[i].a,1.*q[i].k));
		}
	}
	queue<ll> q;
	for (;q.size();q.pop());
	for (q.push(0),dis[0] = 1,vis[0] = true;q.size();) {
		ll now = q.front(); q.pop();
		vis[now] = false;
		for (size_t i = 0;i < edge[now].size();i++) {
			double w = edge[now][i].second;
			ll to = edge[now][i].first;
			if (dis[to] < dis[now]*w) {
				dis[to] = dis[now]*w;
				if ((cnt[to] = cnt[now]+1) >= n) return true;
				if (!vis[to]) { q.push(to); vis[to] = true; }
			}
		}
	}
	return false;
}
signed main() {
	scanf("%lld%lld%lld",&n,&s,&t);
	double l = 0,r = 1e10,mid,ans = -124;
	for (ll i = 1;i <= s;i++) scanf("%lld%lld%lld%lld",&q[i].o,&q[i].a,&q[i].b,&q[i].k);
	for (ll i = 1;i <= t;i++) scanf("%lld%lld",&q[s+i].a,&q[s+i].k),q[s+i].o = 3;
	for (;r-l >= eps;check(mid = (l+r)/2.) ? ans = mid,l = mid+eps : r = mid-eps);
	if (ans < 0) printf("-1");
	else printf("%.9lf",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/lrj124/p/14410178.html