[洛谷P4251] 小凸玩矩阵

问题描述

小凸和小方是好朋友,小方给了小凸一个 \(n × m (n \leq m)\) 的矩阵 A,并且要求小凸从矩阵中选出 n 个数,其中任意两个数都不能在同一行或者同一列。现在小凸想知道,选出的 n 个数中第 k 大的数的最小值是多少。

输入格式

第 1 行读入 3 个整数 n, m, k。

接下来 n 行,每一行有 m 个数字,第 i 行第 j 个数字代表矩阵中第 i 行第 j 列的元素 \(A_{i,j}\)

输出格式

输出包含一行,为选出的 n 个数中第 k 大数的最小值。

数据范围

对于 20% 的数据, \(1 \leq n \leq m \leq 9\)

对于 40% 的数据, \(1 \leq n \leq m \leq 22, 1 \leq n \leq 12\)

对于 100% 的数据, \(1 \leq k \leq n \leq m \leq 250, 1 \leq A_{i,j} \leq 10^9\)

链接

洛谷

解析

每一行每一列都只能有一个点被选中,这与二分图匹配的限制十分相似。不妨往这一方面去考虑。

首先发现答案是具有单调性的,那么先二分一个mid,把矩阵中大于等于mid的数设为1,小于mid的设为0。然后,可以把新产生的矩阵当做一个邻接矩阵,跑二分图最大匹配。如果匹配数量大于等于\(n-mid+1\),说明能找到\(n-mid+1\)以上个比\(mid\)大的满足要求的数,即\(mid\)偏小;反之偏大。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 252
using namespace std;
int n,m,k,i,j,g[N][N],a[N][N],match[N*N];
bool vis[N*N];
int read()
{
    char c=getchar();
    int w=0;
    while(c<'0'||c>'9') c=getchar();
    while(c<='9'&&c>='0'){
        w=w*10+c-'0';
        c=getchar();
    }
    return w;
}
bool dfs(int x)
{
    for(int i=1;i<=m;i++){
        if(g[x][i]&&!vis[n+i]){
            vis[n+i]=1;
            if(!match[n+i]||dfs(match[n+i])){
                match[n+i]=x;
                return 1;
            }
        }
    }
    return 0;
}
int hungary()
{
    int ans=0;
    for(int i=1;i<=n;i++){
        if(!match[i]){
            memset(vis,0,sizeof(vis));
            if(dfs(i)) ans++;
        }
    }
    return ans;
}
bool check(int x)
{
    memset(match,0,sizeof(match));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++) g[i][j]=(a[i][j]<=x);
    }
    if(hungary()>=n-k+1) return true;
    return false;
}
int main()
{
    int l=1,r=0,mid,ans;
    n=read();m=read();k=read();
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            a[i][j]=read();
            r=max(r,a[i][j]);
        }
    }
    while(l<=r){
        mid=(l+r)/2;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/LSlzf/p/12220642.html