题目链接:E. Maximize Mex
题意描述:
初始有个学生,
个队伍
,学生编号
到
给出每个学生的能力值
每个学生初始所在队伍编号
接下去天,每天发生以下事件:
编号为的学生退出他所在的队伍,并且他不再加入其他队伍,接着在
个队伍中,每个队伍至多选出一名学生,组成一个团队
这个团队的总能力值为:在选出的学生的能力值中未出现的最小的非负整数
题目要求:求出这d天的每一天中,能够组成的团队的最大能力值
题目分析:
先考虑如果没有d天的修改,怎么求答案。
我们把学生看成他的能力值和他所在队伍
的一条边,这样每个团队至多只能选出一名学生就对应了每个队伍
只能匹配一次,这样我们可以通过匈牙利算法,依此匹配能力值
,直到匹配失败,就是某一天的团队能力最大值。
接着我们发现d天的修改操作相当于d次删除二分图的边,如果删除了某个边之后,我们重新清空匹配情况,再次使用上面的贪心匹配过程,复杂度不太好计算,我觉得差不多是的吧,这样显然不太可行。
反过来考虑,我们倒着做的话,可以看成只有加边操作,加边之后已有的匹配情况是不用被删除的,那么我们可以倒着求答案:先求出天的答案,加入
天删除的边,然后
天在
天答案的基础上增加答案尝试匹配,一直到匹配失败就是
天的答案,同样的我们可以求出
天的答案。。。
这样思考的话,每次匹配的结果我们都重复利用起来了,复杂度是的(匈牙利是
但实际上
很少能跑满)。
尝试一下代码,只跑了62ms,O(能过)。
在二分图匹配的时候要多考虑点数是否需要开两倍,边数开两倍,之类的问题。
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9+7;
const int MAXN = 10010;
int n, m, d;
struct node{
int v,next;
}e[MAXN<<1];
int head[MAXN], cnt;
inline void add(int u,int v){
e[cnt] = (node){v,head[u]}, head[u] = cnt++;
}
int p[MAXN], c[MAXN], k[MAXN], ans[MAXN];
int linker[MAXN];
bool used[MAXN];
bool dfs(int u){
for(int i = head[u];~i;i = e[i].next){
if(!used[e[i].v]){
used[e[i].v] = 1;
if(linker[e[i].v] == -1 || dfs(linker[e[i].v])){
linker[e[i].v] = u;
return true;
}
}
}
return false;
}
int main() {
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]), c[i] += 5000;
scanf("%d",&d);
for(int i = 1;i<=d;i++){
scanf("%d",&k[i]);
used[k[i]] = 1; //标记删除的边的id
}
memset(linker, -1, sizeof linker);
memset(head, -1, sizeof head);
for(int i = 1;i<=n;i++){
if(!used[i]){ //加入未被删除的边
add(p[i], c[i]);
add(c[i], p[i]);
}
}
while(1){ //先求出最后一天的答案
memset(used, 0, sizeof used);
if(dfs(ans[d]))
ans[d]++;
else break;
}
for(int i = d-1;i>=1;i--){
add(p[k[i+1]], c[k[i+1]]);
add(c[k[i+1]], p[k[i+1]]);
ans[i] = ans[i+1];
while(1){
memset(used, 0, sizeof used);
if(dfs(ans[i]))
ans[i]++;
else break;
}
}
for(int i = 1;i<=d;i++){
printf("%d\n",ans[i]);
}
return 0;
}