CF1139 E. Maximize Mex(二分图匹配)

题目链接:E. Maximize Mex

题意描述:

初始有\large n个学生,\large m个队伍\large (1\leqslant m\leqslant n\leqslant 5000),学生编号\large 1\large n

给出每个学生的能力值\large p_i(0\leqslant p_i\leqslant 5000)

每个学生初始所在队伍编号\large c_i(1\leqslant c_i\leqslant m)

接下去\large d(1\leqslant d\leqslant n)天,每天发生以下事件:

编号为\large k_i(1\leqslant k_i\leqslant n)的学生退出他所在的队伍,并且他不再加入其他队伍,接着在\large m个队伍中,每个队伍至多选出一名学生,组成一个团队

这个团队的总能力值为:在选出的学生的能力值中未出现的最小的非负整数

题目要求:求出这d天的每一天中,能够组成的团队的最大能力值

题目分析:

先考虑如果没有d天的修改,怎么求答案。

我们把学生看成他的能力值\large p_i和他所在队伍\large c_i的一条边,这样每个团队至多只能选出一名学生就对应了每个队伍\large c_i只能匹配一次,这样我们可以通过匈牙利算法,依此匹配能力值\large 0,1,2,...,直到匹配失败,就是某一天的团队能力最大值。

接着我们发现d天的修改操作相当于d次删除二分图的边,如果删除了某个边之后,我们重新清空匹配情况,再次使用上面的贪心匹配过程,复杂度不太好计算,我觉得差不多是\large O(d*n^3)的吧,这样显然不太可行。

反过来考虑,我们倒着做的话,可以看成只有加边操作,加边之后已有的匹配情况是不用被删除的,那么我们可以倒着求答案:先求出\large d天的答案,加入\large d天删除的边,然后\large d-1天在\large d天答案的基础上增加答案尝试匹配,一直到匹配失败就是\large d-1天的答案,同样的我们可以求出\large d-2天的答案。。。

这样思考的话,每次匹配的结果我们都重复利用起来了,复杂度是\large O(n^3)的(匈牙利是\large O(n^3)但实际上\large n^3很少能跑满)。

尝试一下代码,只跑了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;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值