bzoj4443: [Scoi2015]小凸玩矩阵

Description

小凸和小方是好朋友,小方给小凸一个N*M(N<=M)的矩阵A,要求小秃从其中选出N个数,其中任意两个数字不能在同一行或同一列,现小凸想知道选出来的N个数中第K大的数字的最小值是多少。

Input

第一行给出三个整数N,M,K
接下来N行,每行M个数字,用来描述这个矩阵

Output

如题 

Sample Input

3 4 2
1 5 6 6
8 3 4 3
6 8 6 3

Sample Output

3

题解:
看了讨论就发现是二分了,二分ans,判断如果小于ans的有n-k+1个那么ans可以变小。
我们可以用网络流来判断,建边如下:
ins(st,i,1),//表示起点向行连一条边
ins(j+n,ed,1),//表示列向终点连一条边
如果i,j这个点的值<=ansins(i,j+n,1)
限制流量为一,就保证了不会选到在同行或者同列的店
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define inf 0x7fffffff/3 
using namespace std;
const int N=600;
int n,m,k;
int map[N][N];
int l,r=0,st,ed;
struct node{
	int x,y,z,next,other;
}sa[N*N];int len=0,first[N];
void ins(int x,int y,int z)
{
    len++;
    sa[len].x=x;
    sa[len].y=y;
    sa[len].z=z;
    sa[len].next=first[x];
    first[x]=len;
    sa[len].other=len+1;
     
    len++;
    sa[len].x=y;
    sa[len].y=x;
    sa[len].z=0;
    sa[len].next=first[y];
    first[y]=len;
    sa[len].other=len-1;
}
int list[100005],h[100005],head,tail;
bool bt()//宽搜只是为了构建h数组 
{
    memset(h,0,sizeof(h));h[st]=1; //出发点的层次为1
    list[1]=st;head=1;tail=2; //宽搜
    while(head!=tail)
    {
        int x=list[head];
        for(int k=first[x];k!=-1;k=sa[k].next)
        {
            int y=sa[k].y;
            if(  sa[k].z>0 &&  h[y]==0  )//a[k].c>0让多次构图成为可能
            {
                // x >y这条边还有流量,并且 y没有访问过
                h[y]=h[x]+1;   //y作为x的下一层
                list[tail++]=y;
            }
        }
        head++;
    }
    if(h[ed]>0) return true;//如果最后能到ed返回true,否则返回false
    else return false;
}
int findans(int x,int f)//函数值等于:带着“期待流量”f从x出发,最后得到的“实际流量”s。 
{
    if(x==ed) return f; //x如果是终点,那么当前所带的期待目标流量都可以完成 
    int s=0,t;
    for(int k=first[x];k!=-1;k=sa[k].next) // 尝试通过所有孩子结点分散任务
    {
        int y=sa[k].y;  
        if( sa[k].z>0 && h[y]==(h[x]+1) && s<f )//a[k].c>0表示x->y这条边还有流量,且y是x的下一层,且“实际流量”<“期望流量”还没有满
        { 
            s+=(  t=findans(  y  , min(sa[k].z,f-s)  )  ); //从y出发,而附带的“期望流量”是多少呢?; 
            sa[k].z-=t;sa[ sa[k].other ].z+=t; //及时修改边的流量,且同时增加反向边的流量
        }
    }
    if(s==0)h[x]=0;//如果x出发没有流量,x从此不可走
    return s;
}
bool check(int x)
{
	memset(first,-1,sizeof(first));
	len=0;
	for(int i=1;i<=n;i++)ins(st,i,1);
	for(int j=1;j<=m;j++)ins(j+n,ed,1);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(map[i][j]<=x)
			ins(i,j+n,1);
		}
	}
	int ans=0;
	while(bt()!=false)
	{
		ans+=findans(st,inf);
	}
	if(ans>=n-k+1) return true;
	else return false;
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	st=0;ed=n+m+1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&map[i][j]);r=max(map[i][j],r);
		}
	}
	l=0;
	int mid,an;
	while(l<r)
	{
		mid=(l+r)>>1;
		if(check(mid)==true) r=mid,an=mid;
		else l=mid+1;
	}
	printf("%d\n",l);
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值