差分约束算法:给出 \(m\) 个不等式形如 \(x_{a_i} \le x_{b_i} + y_i\),求是否有解。
考虑把不等式看成图上的三角不等式 \(dis_v \le dis_u + d\),连边 \((b_i, a_i, y_i)\),以 \(x_i\) 最大的位置跑最短路,如果图中有负环就无解。此时求出来的 \(dis_i\) 是 \(x_i \le 0\) 的最大解。
考虑二分答案 \(x\),记 \(s\) 为前缀和数组,那么我们可以得到不等式:
- \(s_0 = 0, s_{2n} = x\),连边 \((0, 2n, x), (2n, 0, -x)\);
- \(s_i - s_{i-1} \ge 0\),连边 \((i, i - 1, 0)\);
- \(i \in [0,n), s_{i+n} - s_i \ge a_i\),连边 \((i + n, i, -a_i)\);
- \(i \in [0,n), x - (s_{i+n} - s_i) \ge a_{i+n}\),连边 \((i, i + n, x - a_{i+n})\)。
直接跑 spfa 会 T。注意到删去 \(n\) 点后图长这样:
可以 dp 求出 \(2n\) 到 \(n-1, n+1, 0\) 的最短路,以及 \(n-1\) 到 \(0, n+1\) 的最短路。加上新点 \(n\) 后,只用计算与新加边有关的点,把整张图缩成 \(0, n-1, n, n+1, 2n\) 五个点,在新图上跑 spfa 即可。
总结:这种差分约束但是又不能直接跑 spfa 的题,考虑观察图的形态,用 dp 求出最短路,或者缩点。
code
// Problem: F - Gateau
// Contest: AtCoder - AtCoder Regular Contest 117
// URL: https://atcoder.jp/contests/arc117/tasks/arc117_f
// Memory Limit: 1024 MB
// Time Limit: 3000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define pb emplace_back
#define fst first
#define scd second
#define mems(a, x) memset((a), (x), sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<ll, ll> pii;
const int maxn = 300100;
const ll inf = 0x3f3f3f3f3f3f3f3fLL;
ll n, a[maxn], m, f[maxn], head[maxn], len, g[maxn];
bool vis[maxn];
struct edge {
ll to, dis, next;
} edges[99];
inline void add_edge(ll u, ll v, ll d) {
edges[++len].to = v;
edges[len].dis = d;
edges[len].next = head[u];
head[u] = len;
}
inline bool spfa() {
queue<int> q;
mems(f, 0x3f);
mems(vis, 0);
mems(g, 0);
f[m] = 0;
g[m] = 1;
vis[m] = 1;
q.push(m);
while (q.size()) {
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = head[u]; i; i = edges[i].next) {
ll v = edges[i].to, d = edges[i].dis;
if (f[v] > f[u] + d) {
f[v] = f[u] + d;
if (!vis[v]) {
vis[v] = 1;
if ((++g[v]) >= 8) {
return 0;
}
q.push(v);
}
}
}
}
return 1;
}
inline bool check(ll x) {
len = 0;
mems(head, 0);
mems(f, 0);
f[m - 1] = f[m] = 0;
f[n - 1] = -a[n - 1];
for (int i = n - 2; i; --i) {
f[i] = f[i + 1];
f[i + n] = f[i + n + 1];
ll p = f[i], q = f[i + n];
f[i] = min(f[i], q - a[i]);
f[i + n] = min(f[i + n], p + x - a[i + n]);
}
f[0] = f[1];
add_edge(m, 0, f[0]);
add_edge(m, n + 1, f[n + 1]);
add_edge(m, n - 1, f[n - 1]);
mems(f, 0);
f[n - 1] = 0;
f[m - 1] = x - a[m - 1];
for (int i = n - 2; i; --i) {
f[i] = f[i + 1];
f[i + n] = f[i + n + 1];
ll p = f[i], q = f[i + n];
f[i] = min(f[i], q - a[i]);
f[i + n] = min(f[i + n], p + x - a[i + n]);
}
f[0] = f[1];
add_edge(n - 1, n + 1, f[n + 1]);
add_edge(n - 1, 0, f[0]);
add_edge(n, n - 1, 0);
add_edge(n + 1, n, 0);
add_edge(n, 0, -a[0]);
add_edge(m, n, -a[n]);
add_edge(0, m, x);
add_edge(m, 0, -x);
return spfa();
}
void solve() {
scanf("%lld", &n);
m = (n << 1);
ll l = 0, r = 1e10, ans = -1;
for (int i = 0; i < m; ++i) {
scanf("%lld", &a[i]);
}
if (n == 1) {
printf("%lld\n", a[0] + a[1]);
return;
}
for (int i = 0; i < n; ++i) {
l = max(l, a[i] + a[i + n]);
}
while (l <= r) {
ll mid = (l + r) >> 1;
if (check(mid)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
printf("%lld\n", ans);
}
int main() {
int T = 1;
// scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}
- AtCoder Regular Contest Gateau 117atcoder regular contest gateau atcoder regular contest 165 minimization atcoder regular contest atcoder regular contest 166 atcoder regular contest degree atcoder regular contest 167 atcoder regular contest sliding atcoder regular contest 164 atcoder regular contest builder subsegments atcoder regular contest