最小费用最大流
https://www.luogu.com.cn/problem/P4016
有 \(n\) 个人围成一圈,每个人都可以给相邻的人糖果,为最少传递多少次可以使得所有人的糖果数一样。
Tutorial这道题其实可以不用费用流写,有 \(O(n^2)\) 的简单写法,但是先说费用流。
Flow
由于每个人最后的值是确定的,所以先算出糖果数的平均值 \(avg\) ,然后如果一个人的糖果数大于平均,那么他一定要给别人糖,连接 \(S\) 和 \(i\) ,费用为 \(0\), 容量为 \(a_i - avg\), 表示他可以从原点免费想其他人传递这么多流量,如果一个人小于平均,那么连接 \(i\) 和 \(T\) ,费用为 \(0\), 容量为 \(avg - a_i\)
然后将每个人向他相邻的人连接费用为 \(1\), 容量为 INF 的边,表示每向相邻的人转移一个糖果需要花费1。这样一来最大流可以确保最终所有人糖果一样(一定是流满的情况),然后最小费用就是答案
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <sstream>
#include <vector>
#define endl '\n'
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
#define P pair<int, int>
typedef long long ll;
using namespace std;
typedef long long ll;
const int maxn = 120;
const ll inf = 1e18;
int n, cnt_edge = 1, S, T;
int a[maxn], head[maxn];
ll dis[maxn];
bool vis[maxn];
struct edge {
int to, nxt;
ll flow, cost;
} e[(maxn * maxn) << 2];
inline void add(int u, int v, ll w, ll c) {
e[++cnt_edge].nxt = head[u];
head[u] = cnt_edge;
e[cnt_edge].to = v;
e[cnt_edge].flow = w;
e[cnt_edge].cost = c;
}
inline void addflow(int u, int v, ll w, ll c) {
// cout << u << " " << v << endl;
add(u, v, w, c);
add(v, u, 0, -c);
}
inline bool spfa() {
memset(vis, 0, sizeof(vis));
for (int i = 0; i <= T; i++) dis[i] = inf;
queue<int> q;
q.push(S);
dis[S] = 0;
vis[S] = 1;
while (!q.empty()) {
int x = q.front();
q.pop();
vis[x] = 0;
for (int i = head[x]; i; i = e[i].nxt) {
int y = e[i].to;
// cout << "->" << y << endl;
if (e[i].flow && dis[y] > dis[x] + e[i].cost) {
dis[y] = dis[x] + e[i].cost;
if (!vis[y]) q.push(y), vis[y] = 1;
}
}
}
return dis[T] != inf;
}
ll dfs(int x, ll lim) {
vis[x] = 1;
if (x == T || lim <= 0) return lim;
ll res = lim;
for (int i = head[x]; i; i = e[i].nxt) {
int y = e[i].to;
if (dis[y] != dis[x] + e[i].cost || e[i].flow <= 0 || vis[y]) continue;
ll tmp = dfs(y, min(res, e[i].flow));
res -= tmp;
e[i].flow -= tmp;
e[i ^ 1].flow += tmp;
if (res <= 0) break;
}
return lim - res;
}
inline ll Dinic() {
ll res = 0, cost = 0;
while (spfa()) {
ll flow = dfs(S, inf);
res += flow, cost += flow * dis[T];
}
return cost;
}
int main() {
cin >> n;
S = n + 1, T = n + 2;
int avg = 0;
for (int i = 1; i <= n; i++) cin >> a[i], avg += a[i];
avg /= n;
for (int i = 1; i <= n ; i++) {
if (a[i] > avg) {
addflow(S, i, a[i] - avg, 0);
} else if (a[i] < avg) {
addflow(i, T, avg - a[i], 0);
}
}
for (int i = 1; i <= n; i++) {
if (i != n)
addflow(i, i + 1, inf, 1);
else
addflow(i, 1, inf, 1);
if (i != 1)
addflow(i, i - 1, inf, 1);
else
addflow(i, n, inf, 1);
}
// cout << "---\n";
cout << Dinic();
return 0;
}
直接解
其实可以发现,如果不是一个环的话,可以 \(O(n)\) 贪心地解决,这个人当前的糖数和目标糖数一定从下一个人处传来,容易计算答案。
考虑连成环的情况,如果是一个环的话,一定会有相邻的两人 \(A, B\) 不需要传递糖果,因为 \(A\) 可以从他左边得到糖果或者传递给左边 \(B\) 也可以只与右边交换。这样就可以短环为链。
枚举断点,\(O(n)\) 统计答案,最后输出最小值即可。