最小费用最大流
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)\) 统计答案,最后输出最小值即可。