[SCOI2015]小凸玩矩阵

题目

题目

做法

很明显的一个事情,求第 K K K大的最小值,一般采用的做法是二分(有人会问,但是不满足二分性啊,接着往下看)。

考虑二分答案,但是如何检验 m i d mid mid是对还是错,考虑每次只能取 < = m i d <=mid <=mid的数字,如果能取到 ( n − k ) + 1 (n-k)+1 (nk)+1个数字以上就可以,至于看能否取到 n − k + 1 n-k+1 nk+1个,采用二分图匹配,左边的点是行,右边的点是列,一个格子能被取,就把其行和列连边。

为什么可以满足二分性,他们认为不满足二分性就只有一个原因,如果我们假定的第 k k k大的数字太大的话,可能根本取不到 k k k个大于等于这个数字的格子,但是可以取到 n − k + 1 n-k+1 nk+1个以上小于等于这个数字的格子。

但是我们从函数的角度来证明。

我们设当我们假定的第 k k k大的数字为 t t t时,小于等于 t t t的格子最多取 f ( t ) f(t) f(t)个。

那么很明显 f ( t ) ≤ f ( t + 1 ) f(t)≤f(t+1) f(t)f(t+1),那么如果 f ( t − 1 ) < n − k + 1 , f ( t ) ≥ n − k + 1 f(t-1)<n-k+1,f(t)≥n-k+1 f(t1)<nk+1,f(t)nk+1,那么 t t t就是答案。

f ( t − 1 ) f(t-1) f(t1)最多取 n − k n-k nk个,同时 f ( t ) ≥ n − k + 1 f(t)≥n-k+1 f(t)nk+1,取 f ( t ) f(t) f(t)个小于等于 t t t的格子, n − f ( t ) n-f(t) nf(t)个大于 t t t的格子,此时第 k k k大数小于等于 t t t,且因为 f ( t − 1 ) ≠ f ( t ) f(t-1)≠f(t) f(t1)=f(t),所以存在 t t t的格子且为第 k k k大,所以构造出了第 k k k大为 t t t的方案。

所以只要存在 f ( t − 1 ) < n − k + 1 , f ( t ) ≥ n − k + 1 f(t-1)<n-k+1,f(t)≥n-k+1 f(t1)<nk+1,f(t)nk+1,就可以构造出第 k k k t t t的方案。

现在证明假如存在最小答案 t t t,其一定满足 f ( t − 1 ) < n − k + 1 , f ( t ) ≥ n − k + 1 f(t-1)<n-k+1,f(t)≥n-k+1 f(t1)<nk+1,f(t)nk+1,首先, f ( t ) f(t) f(t)不用证明了(显然),但是 f ( t − 1 ) f(t-1) f(t1)吗,首先如果 f ( t − 1 ) ≥ n − k + 1 f(t-1)≥n-k+1 f(t1)nk+1,则一定存在 i i i使得 f ( i ) < n − k + 1 f(i)<n-k+1 f(i)<nk+1 i < t − 1 i<t-1 i<t1且使得 ∀ i < j ≤ t − 1 , f ( j ) > = n − k + 1 ∀i<j≤t-1,f(j)>=n-k+1 i<jt1,f(j)>=nk+1,这样 i + 1 i+1 i+1也是答案,但是 i + 1 < t i+1<t i+1<t,矛盾,得证。

且由于 f ( i ) ≤ f ( i + 1 ) f(i)≤f(i+1) f(i)f(i+1),简单来说就是找唯一一个满足 f ( t − 1 ) < n − k + 1 , f ( t ) ≥ n − k + 1 f(t-1)<n-k+1,f(t)≥n-k+1 f(t1)<nk+1,f(t)nk+1的位置,可以直接二分。

#include<cstdio>
#include<cstring>
#define  N  310
#define  NN  130000
using  namespace  std;
inline  int  mymax(int  x,int  y){return  x>y?x:y;}
inline  int  mymin(int  x,int  y){return  x<y?x:y;}
int  val[N][N],n,m,K,floor_limit,ceil_limit;

bool  ma[N][N],v[N];
int  match[N];
bool  find_match(int  x)
{
	v[x]=1;
	for(int  i=1;i<=m;i++)
	{
		if(!ma[x][i])continue;
		if(!match[i]  ||  (!v[match[i]]  &&  find_match(match[i]))){match[i]=x;return  1;}
	}
	return  0;
}
bool  pd(int  k)
{
	memset(ma,0,sizeof(ma));
	for(int  i=1;i<=n;i++)
	{
		for(int  j=1;j<=m;j++)
		{
			if(val[i][j]<=k)ma[i][j]=1;
		}
	}
	memset(match,0,sizeof(match));
	int  ans=0;
	for(int  i=1;i<=n;i++)
	{
		memset(v,0,sizeof(v));
		ans+=find_match(i);
	}
	return  ans>=(n-K+1);
}
int  main()
{
	scanf("%d%d%d",&n,&m,&K);
	floor_limit=1;ceil_limit=1;
	for(int  i=1;i<=n;i++)
	{
		for(int  j=1;j<=m;j++)
		{
			scanf("%d",&val[i][j]);
			ceil_limit=mymax(ceil_limit,val[i][j]);
		}
	}
	int  l=floor_limit,r=ceil_limit,mid,ans=ceil_limit;
	while(l<=r)
	{
		mid=(l+r)/2;
		if(pd(mid)==1)ans=mid,r=mid-1;
		else  l=mid+1;
	}
	printf("%d\n",ans);
	return  0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值