原题:P2857 [USACO06FEB] Steady Cow Assignment G
题面:
P2857 [USACO06FEB] Steady Cow Assignment G
题目描述
农夫约翰的 头牛()各自居住在 个谷仓中的一个(),当然,谷仓的容量是有限的。有些牛非常喜欢她们当前的谷仓,而有些则不太开心。
FJ 想要重新安排这些牛,使得牛群的快乐程度尽可能均衡,即使这意味着所有的牛都讨厌她们被分配的谷仓。
每头牛都会告诉 FJ 她对谷仓的偏好顺序。牛对特定分配的快乐程度是她对该谷仓的排名。你的任务是找到一种将牛分配到谷仓的方法,使得没有谷仓的容量被超出,并且牛给她们被分配的谷仓的排名范围(即最高排名谷仓和最低排名谷仓之间的正差加一)的大小尽可能小。
输入格式
第 1 行:两个用空格分隔的整数, 和
第 2 行到第 行:每行包含 个用空格分隔的整数,正好是 的某种顺序排列。第 行的第一个整数是第 头牛最喜欢的谷仓的编号,第二个整数是第 头牛次喜欢的谷仓的编号,依此类推。
第 行: 个用空格分隔的整数,分别是第一个谷仓的容量,第二个谷仓的容量,依此类推。这些数字的总和保证至少为 。
输出格式
第 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 翻译)
我们先不考虑怎么让范围最小,先看看如何判断所有的牛都有牛棚住,且所有牛棚均未超出容量。我们不妨把牛的头数看作图上的流量,则问题转化为假设在当前限定条件下求图上的最大流。
为什么可以这样做?我们当前有 头牛,现在要把它们全部塞到牛棚里面,将全部的牛看成一个整体,全部的牛棚看成一个整体,牛 牛棚的移动就可以看作流量的转移。将牛的整体看作一个超级源点 ,牛棚的整体看作一个超级汇点 ,设当前牛为 ,牛棚为 ,则边 的容量就是 ,因为每头牛只能被分配一次,边 的容量为 ,因为一头牛只能进入一个牛棚,边 的容量为 ,因为每个牛棚可以容纳 头牛。则我们的图就建模完成了,在这个图上面跑最大流,如果最大流等于 ,则说明当前的限定条件是合理的,否则需要修改使其更加宽松。
现在我们来考虑如何求最小范围。假设当前已知范围 是可行的,则显然范围 也是可行的。即对于一个范围大小 ,若其可行,则 必然可行。显然这类问题可以使用二分答案求解,因为具有单调关系。
其实这道题暴力枚举也是可以过的,因为 实在是太小了。
#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;
}
总体时间复杂度: