Maximize Mex
题目大概意思:
有 名学生和 个社团,第 名学生的能力值是 ,初始时所在的社团为 . 在为期 天的活动中:在第 天的早上,编号为 的学生会退出所在的社团并永久离开,下午会从每个社团选出 名学生参加比赛(如果某个社团已经没有学生,则不再从该社团选择学生)。第 天的比赛的得分是第 天的参加比赛的学生的能力值组成的集合 中最小的没有出现的非负整数。求 天中每一天的比赛的最大得分。
其中,
分析:
首先考虑没有学生退出的简单情况,那么问题就变为了求出最小的无法被满足的能力值。而这可以使用二分图匹配来实现:把社团和能力值看作二分图中的节点,学生看作二分图中的边,连接该学生所在的社团和其能力值。由于在二分图匹配的过程中,后续的匹配不会导致之前已经匹配的节点失配,因此只需按能力值从小到大逐一进行 ,直到找到无法匹配的节点为止即可。
在考虑有学生退出的情况下,由于二分图匹配可以看作特殊情况下的最大流问题,而在求解最大流问题时动态删除图中的边会导致之前已匹配的节点失配,不易确定最小的失配节点。因此我们可以把删除边看作时间上倒序的添加边,在动态添加边的过程中进行二分图匹配,每一次记录下当前最小的失配节点。由于最大流算法在任意残余网络均可找到最大流,故该算法是正确的。
下面贴代码:
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int MAX_N = 5005;
const int MAX_V = 10008;
vector<int> G[MAX_V];
int V;
int match[MAX_V];
bool used[MAX_V];
int p[MAX_N], c[MAX_N], k[MAX_N];
int ans[MAX_N];
void add_edge(int u, int v);
bool dfs(int v);
int main()
{
int n, m, d;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
{
scanf("%d", p + i);
}
for (int i = 1; i <= n; ++i)
{
scanf("%d", c + i);
}
scanf("%d", &d);
for (int i = 0; i < d; ++i)
{
scanf("%d", k + i);
used[k[i]] = true;
}
for (int i = 1; i <= n; ++i)
{
if (!used[i])
{
add_edge(p[i], c[i] + MAX_N);
}
}
memset(match, -1, sizeof(match));
memset(used, 0, sizeof(used));
int cur = 0;
while (match[cur] != -1 || dfs(cur))
{
++cur;
memset(used, 0, sizeof(used));
}
for (int i = d - 1; i >= 0; --i)
{
add_edge(p[k[i]], c[k[i]] + MAX_N);
ans[i] = cur;
memset(used, 0, sizeof(used));
while (match[cur] != -1 || dfs(cur))
{
++cur;
memset(used, 0, sizeof(used));
}
}
for (int i = 0; i < d; ++i)
{
printf("%d\n", ans[i]);
}
return 0;
}
void add_edge(int u, int v)
{
G[u].push_back(v);
G[v].push_back(u);
}
bool dfs(int v)
{
used[v] = true;
for (int i = 0; i < G[v].size(); i++)
{
int u = G[v][i];
int w = match[u];
if (w == -1 || !used[w] && dfs(w))
{
match[v] = u;
match[u] = v;
return true;
}
}
return false;
}