洛谷P2857 [USACO06FEB] Steady Cow Assignment G

29 阅读5分钟

原题:P2857 [USACO06FEB] Steady Cow Assignment G

题面:

P2857 [USACO06FEB] Steady Cow Assignment G

题目描述

农夫约翰的 NN 头牛(1N10001 \leq N \leq 1000)各自居住在 BB 个谷仓中的一个(1B201 \leq B \leq 20),当然,谷仓的容量是有限的。有些牛非常喜欢她们当前的谷仓,而有些则不太开心。

FJ 想要重新安排这些牛,使得牛群的快乐程度尽可能均衡,即使这意味着所有的牛都讨厌她们被分配的谷仓。

每头牛都会告诉 FJ 她对谷仓的偏好顺序。牛对特定分配的快乐程度是她对该谷仓的排名。你的任务是找到一种将牛分配到谷仓的方法,使得没有谷仓的容量被超出,并且牛给她们被分配的谷仓的排名范围(即最高排名谷仓和最低排名谷仓之间的正差加一)的大小尽可能小。

输入格式

第 1 行:两个用空格分隔的整数,NNBB

第 2 行到第 N+1N+1 行:每行包含 BB 个用空格分隔的整数,正好是 1..B1..B 的某种顺序排列。第 i+1i+1 行的第一个整数是第 ii 头牛最喜欢的谷仓的编号,第二个整数是第 ii 头牛次喜欢的谷仓的编号,依此类推。

N+2N+2 行:BB 个用空格分隔的整数,分别是第一个谷仓的容量,第二个谷仓的容量,依此类推。这些数字的总和保证至少为 NN

输出格式

第 1 行:一个整数,牛给她们被分配的谷仓的排名范围的最小值,包括端点。

输入输出样例 #1

输入 #1

6 4
1 2 3 4
2 3 1 4
4 2 3 1
3 1 2 4
1 3 4 2
1 4 2 3
2 1 3 2

输出 #1

2

说明/提示

样例解释:

每头牛可以被分配到她们的第一或第二选择:谷仓 1 得到牛 1 和 5,谷仓 2 得到牛 2,谷仓 3 得到牛 4,谷仓 4 得到牛 3 和 6。 (由 ChatGPT 4o 翻译)

SolutionSolution

我们先不考虑怎么让范围最小,先看看如何判断所有的牛都有牛棚住,且所有牛棚均未超出容量。我们不妨把牛的头数看作图上的流量,则问题转化为假设在当前限定条件下求图上的最大流。

为什么可以这样做?我们当前有 nn 头牛,现在要把它们全部塞到牛棚里面,将全部的牛看成一个整体,全部的牛棚看成一个整体,牛 \rightarrow 牛棚的移动就可以看作流量的转移。将牛的整体看作一个超级源点 ss ,牛棚的整体看作一个超级汇点 tt ,设当前牛为 ii ,牛棚为 jj ,则边 sis \rightarrow i 的容量就是 11 ,因为每头牛只能被分配一次,边 iji \rightarrow j 的容量为 11 ,因为一头牛只能进入一个牛棚,边 jtj \rightarrow t 的容量为 capjcap_j ,因为每个牛棚可以容纳 capjcap_j 头牛。则我们的图就建模完成了,在这个图上面跑最大流,如果最大流等于 nn ,则说明当前的限定条件是合理的,否则需要修改使其更加宽松。

现在我们来考虑如何求最小范围。假设当前已知范围 [l,r][l,r] 是可行的,则显然范围 [l,r][l,r][l',r'] \supseteq [l,r] 也是可行的。即对于一个范围大小 rangerange ,若其可行,则 range>rangerange'>range 必然可行。显然这类问题可以使用二分答案求解,因为具有单调关系。

其实这道题暴力枚举也是可以过的,因为 BB 实在是太小了。

CodingCoding

#include <iostream>
#include <cstring>
#include <iomanip>
#include <cmath>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;

#define ll long long
#define ull unsigned long long
#define debug(x) cout << #x << "=" << x << "\n";

int n, b;
const int maxn = 1e3 + 10, maxb = 30;
const int max_node = 1e5 + 10, maxm = 1e6 + 10;
const int INF = 1e9;
int s, t;
int prefer[maxn][maxb];
struct Edge
{
    int to, cap, next;
    int weight;
} edge[max_node];
Edge temp[max_node];
int head[max_node], cur[max_node], level[max_node];
int tot = 2;

void add_edge(int u, int v, int c, int w)
{
    edge[tot] = {v, c, head[u], w};
    head[u] = tot++;
    edge[tot] = {u, 0, head[v], w};
    head[v] = tot++;
}

bool bfs(int s, int t, int l, int r)
{
    queue<int> q;
    q.push(s);
    memset(level, -1, sizeof(level));
    level[s] = 0;

    while (!q.empty())
    {
        int u = q.front();
        q.pop();

        if (u == t)
            return true;

        for (int i = head[u]; i; i = temp[i].next)
        {
            int v = temp[i].to;

            if (level[v] == -1 && temp[i].cap > 0 && ((temp[i].weight >= l && temp[i].weight <= r) || temp[i].weight == INF))
            {
                level[v] = level[u] + 1;
                q.push(v);
            }
        }
    }

    return false;
}

int dfs(int u, int t, int flow, int l, int r)
{
    if (u == t)
        return flow;

    int used = 0;
    for (int &i = cur[u]; i; i = temp[i].next)
    {
        int v = temp[i].to, cap = temp[i].cap;
        int w = temp[i].weight;

        if (cap <= 0)
            continue;

        if (level[v] == level[u] + 1 && ((l <= w && w <= r) || w == INF))
        {
            int ret = dfs(v, t, min(flow - used, cap), l, r);
            if (ret)
            {
                temp[i].cap -= ret;
                temp[i ^ 1].cap += ret;
                used += ret;
                if (used == flow)
                    break;
            }
        }
    }

    return used;
}

int dinic(int s, int t, int l, int r)
{
    int max_flow = 0;
    while (bfs(s, t, l, r))
    {
        memcpy(cur, head, sizeof(head));
        max_flow += dfs(s, t, INF, l, r);
    }

    return max_flow;
}

bool check(int range)
{
    for (int l = 1; l + range - 1 <= b; l++)
    {
        memcpy(temp, edge, sizeof(edge));
        int r = l + range - 1;
        int max_flow = dinic(s, t, l, r);
        if (max_flow == n)
            return true;
    }

    return false;
}

int binary(int left, int right)
{
    int ans, mid;

    while (left <= right)
    {
        mid = (left + right) >> 1;

        if (check(mid))
        {
            ans = mid;
            right = mid - 1;
        }
        else
            left = mid + 1;
    }

    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> b;
    s = 0, t = n + b + 1;
    for (int i = 1; i <= n; i++)
    {
        add_edge(s, i, 1, INF);
        for (int j = 1; j <= b; j++)
        {
            cin >> prefer[i][j];
            add_edge(i, prefer[i][j] + n, 1, j);
        }
    }
    for (int i = 1; i <= b; i++)
    {
        int cap;
        cin >> cap;
        add_edge(i + n, t, cap, INF);
    }

    cout << binary(1, b);

    return 0;
}

总体时间复杂度: O(NB2(N+B)logB)O(NB^2 \sqrt(N + B)log B)