差分约束学习笔记

差分约束可以求出这样的不等式组的 \(\{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_a\ge x_b+c\implies x_b \le x_a-c\),所以连 \(a \stackrel{-c}{\longrightarrow} b\)

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

信息三:\(x_a=x_b\implies x_a \le x_b,x_b \le x_a\),所以连 \(a\stackrel{0}{\longrightarrow} b\)\(b\stackrel{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\) 个小朋友,\(\text{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,i\in [1,n]\),令 \(x_0 = 0\),则 \(x_i \ge x_0+1\)

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

k=1\(x_a=x_b\implies x_a \ge x_b,x_b \ge x_a\),连 \(a\stackrel{0}{\longrightarrow} b\)\(b\stackrel{0}{\longrightarrow} a\)

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

k=3\(x_a \ge x_b\),连 \(b\stackrel{0}{\longrightarrow} a\)

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

k=3\(x_b \ge x_a\),连 \(a\stackrel{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_0\implies x_A \ge c_Ax_0,x_0\ge \frac{1}{c_A}x_A\),连 \(0\stackrel{c_A}{\longrightarrow} A\)\(A\stackrel{\frac{1}{c_A}}{\longrightarrow} 0\)

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

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

对选手 B 立下 Flag 2 的选手 A 要满足 \(x_B < (k+T)x_A\) 也就是 \(x_A > \frac{1}{(k+T)}x_B\),连 \(B\stackrel{\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;
}
posted @ 2021-02-17 23:18  lrj124  阅读(179)  评论(0编辑  收藏  举报