一个
n
×
m
(
n
≤
m
)
n\times m(n\leq m)
n×m(n≤m)的矩阵,现在要从中取出
n
n
n个数字,满足任意两个数字不在同一行或同一列,请问取出来的
n
n
n个数字的第
k
k
k大最小是多少。
首先考虑二分,转换为判定问题。
只要能取出
n
−
k
+
1
n-k+1
n−k+1个小于等于它的数,那么它就可以作为第
k
k
k大。
下面就是建边方法,很显然
- 首先把源点和所有行连起来
- 把所有列和汇点连起来
- 对于矩阵中的一个数,如果它小于二分的值,就把它所在的行和列连起来
然后跑最大流检验就可以了。
c
o
d
e
:
code:
code:
/*
二分判定
建边思路:
造出n+m个点
表示每一行和列
源点向它们连一条边,流量1
如果一个点小于判定值,就把对应的行和列连起来
*/
#include <bits/stdc++.h>
int l,r;
int n,m,k;
int maxflow;
int S=1,T=2;
int deep[1000];
int cur[1000];
int a[333][333];
int head[1000],tot=1;
std::queue<int>q;
struct edge{
int to;
int nxt;
int flow;
}e[400000];
void add(int x,int y,int flow){
e[++tot]={y,head[x],flow};
head[x]=tot;
e[++tot]={x,head[y],0};
head[y]=tot;
}
bool bfs(){
memset(deep,0,sizeof deep);
deep[S]=1;
q.push(S);
while(!q.empty()){
int X=q.front();
q.pop();
for(int i=head[X];i;i=e[i].nxt){
int y=e[i].to;
if(!deep[y]&&e[i].flow){
deep[y]=deep[X]+1;
q.push(y);
}
}
}
return deep[T];
}
int dfs(int x,int flow){
if(x==T||!flow)
return flow;
int Flow=0;
for(int &i=cur[x];i;i=e[i].nxt){
int y=e[i].to;
if(e[i].flow&&deep[y]==deep[x]+1){
int w=dfs(y,std::min(flow,e[i].flow));
if(w){
e[i].flow-=w;
e[i^1].flow+=w;
Flow+=w;
flow-=w;
if(!flow)
break;
}
}
}
return Flow;
}
int dinic(){
maxflow=0;
while(bfs()){
memcpy(cur,head,sizeof head);
for(;;){
int w=dfs(S,0x3f3f3f3f);
if(!w)
break;
maxflow+=w;
}
}
return maxflow;
}
bool check(int x){
memset(e,0,sizeof e);
memset(head,0,sizeof head);
tot=1;
for(int i=1;i<=n;++i)
add(S,2+i,1);
for(int i=1;i<=m;++i)
add(2+n+i,T,1);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(a[i][j]<=x)
add(2+i,2+n+j,1);
return dinic()>=n-k+1;
}
main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&a[i][j]);
l=0,r=1e9;
while(l<r){
int mid=l+r>>1;
if(check(mid))
r=mid;
else
l=mid+1;
}
printf("%d\n",l);
return 0;
}