bzoj4443[SCOI2015]小凸玩矩阵
题意:一个n*m的矩阵(n<=m<=250),要求选出n个数(每行,每列最多选一个),求第k大数的最小值。
首先第k大的意思是从大到小的第k个数(我读错了,WA了一次还以为算法不对...)
然后第k大最小不好直接做.考虑二分答案.
二分答案的单调性在于,如果不能选出n-k+1个小于等于i的不同行列的数,那么最终的答案大于i,否则最终的答案小于等于i。
判定的时候将每一行看作一个点,每一列也看作一个点,每个位置A[i][j](A[i][j]小于等于判定的答案ans)代表一条第i行和第j行之间的边。(这种二维矩阵每一行怎么样怎么样每一列怎么样怎么样的题常常可以转化成二分图去考虑)。
最后二分出一个i,使得我们可以选出n-k+1个小于等于i的数但不能选出n-k+1个小于等于(i-1)的数,这个i就是答案.
这里有一点问题,就是我们虽然可以选出n-k+1个小于等于i的数,但似乎并没有保证可以选出k-1个大于i的数.因此我在读错题WA了一发之后开始怀疑算法正确性。
所幸,随便选出剩下k-1个数一定可以保证第k大的数字是i.
证明如下:
首先,我们选出所有数字之后,不可能选出n-k+1个小于等于(i-1)的数(否则与二分的结果相违背)。也就是说,我们在选出n-k+1个小于等于i的数之后,随便选剩下的k-1个数字,最终选出的全部数字中至少有k个大于等于i.
于是我们有两个条件:全部数字中,至少有n-k+1个小于等于i(二分条件保证可以先选出n-k+1个这样的数,这个条件等价于从小到大第n-k+1个数也就是从大到小第k个数一定小于等于i),至少有k个大于等于i(也就是说从大到小第k个数一定大于等于i),那么第k大的数既小于等于i又大于等于i,所以第k大的数只能是i,这样就没有问题了。
(似乎别人的题解都觉得这是显然的)
(写了个dinic结果比别人的匈牙利慢好多)
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=255; int n,m,k; int a[maxn][maxn]; struct edge{ int to,next,w; }lst[maxn*maxn*3];int len=0,first[maxn*3],_first[maxn*3]; void addedge(int a,int b,int w){ lst[len].to=b;lst[len].next=first[a];lst[len].w=w;first[a]=len++; lst[len].to=a;lst[len].next=first[b];lst[len].w=0;first[b]=len++; } int s,t; int q[maxn*3],vis[maxn*3],T,dis[maxn*3],head,tail; bool bfs(){ vis[s]=++T;head=tail=0; q[tail++]=s;dis[s]=1; while(head!=tail){ int x=q[head++]; for(int pt=first[x];pt!=-1;pt=lst[pt].next){ if(lst[pt].w&&vis[lst[pt].to]!=T){ vis[lst[pt].to]=T;dis[lst[pt].to]=dis[x]+1; q[tail++]=lst[pt].to; } } } if(vis[t]==T)memcpy(_first,first,sizeof(first)); return vis[t]==T; } int dfs(int x,int lim){ if(x==t)return lim; int flow=0,a; for(int pt=_first[x];pt!=-1;pt=lst[pt].next){ _first[x]=pt; if(lst[pt].w&&dis[lst[pt].to]==dis[x]+1&&(a=dfs(lst[pt].to,min(lst[pt].w,lim-flow)))){ lst[pt].w-=a;lst[pt^1].w+=a;flow+=a; if(flow==lim)break; } } return flow; } int dinic(){ int ans=0,x; while(bfs()) while(x=dfs(s,0x7f7f7f7f))ans+=x; return ans; } bool check(int ans){ memset(first,-1,sizeof(first));len=0; s=0;t=n+m+1; for(int i=1;i<=n;++i){ addedge(s,i,1); } for(int i=1;i<=m;++i){ addedge(n+i,t,1); } for(int i=1;i<=n;++i){ for(int j=1;j<=m;++j){ if(a[i][j]<=ans)addedge(i,n+j,1); } } return dinic()>=k; } int main(){ scanf("%d%d%d",&n,&m,&k); k=n-k+1; for(int i=1;i<=n;++i){ for(int j=1;j<=m;++j){ scanf("%d",&a[i][j]); } } int l=0,r=1000000000; while(l<=r){ int mid=(l+r)>>1; if(check(mid)){ r=mid-1; }else{ l=mid+1; } } printf("%d\n",r+1); return 0; }