差分约束学习笔记
差分约束可以求出这样的不等式组的 \(\{x_i\}\) 的一组整数解
对于一个不等式可以移个项 \(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;
}