Luogu 4251 [SCOI2015]小凸玩矩阵
BZOJ 4443
二分答案 + 二分图匹配
外层二分一个最小值,然后检验是否能选出$n - k + 1$个不小于当前二分出的$mid$的数。对于每一个$a_{i, j} \geq mid$,从$i$向$j + n$连一条边,然后跑二分图最大匹配即可。
菜的很,二分图匹配都写不对……
注意数组要开到双倍$n$。
时间复杂度$O(nmlogn)$。
Code:
#include <cstdio> #include <cstring> using namespace std; const int N = 505; const int M = 2e5 + 5; const int inf = 1 << 30; int n, m, k, a[N][N], tot, head[N], mat[N]; bool vis[N]; struct Edge { int to, nxt; } e[M]; inline void add(int from, int to) { e[++tot].to = to; e[tot].nxt = head[from]; head[from] = tot; } inline void read(int &X) { X = 0; char ch = 0; int op = 1; for(; ch > '9'|| ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } bool dfs(int x) { for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(vis[y]) continue; vis[y] = 1; if(!mat[y] || dfs(mat[y])) { mat[y] = x; return 1; } } return 0; } inline bool chk(int mid) { memset(head, 0, sizeof(head)); tot = 0; memset(mat, 0, sizeof(mat)); for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) if(a[i][j] <= mid) add(i, j + n); int res = 0; for(int i = 1; i <= n; i++) { memset(vis, 0, sizeof(vis)); if(dfs(i)) ++res; } return res >= n - k + 1; } int main() { read(n), read(m), read(k); for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) read(a[i][j]); int ln = 0, rn = inf, mid, res; for(; ln <= rn; ) { mid = ((ln + rn) >> 1); if(chk(mid)) rn = mid - 1, res = mid; else ln = mid + 1; } printf("%d\n", res); return 0; }