有n个人,m个洞。每个洞能容纳一个人,每个人到每个洞需要花费一些时间。每个人到达一个洞后可以花C的时间来挖洞,这样该洞的容量就变成2了。求能使至少K个人进洞的最短时间。
还是将求极值问题转化为判定问题。二分枚举时间,能否在time的时间内让至少K个人进洞?求进洞人数当然用二分匹配做。g[i][j]为第i个人到第j个洞的时间。如果g[i][j] <= time,那么由i向j连边。挖洞的情况呢?如果g[i][j] + C <= time的话,由i向j和j+m分别连边,这样就能保证在时间够的情况下改变洞的容量了。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<fstream>
#include<sstream>
#include<cstdlib>
#include<vector>
#include<string>
#include<cstdio>
#include<bitset>
#include<queue>
#include<stack>
#include<cmath>
#include<map>
#include<set>
#define FF(i, a, b) for(int i=a; i<b; i++)
#define FD(i, a, b) for(int i=a; i>=b; i--)
#define REP(i, n) for(int i=0; i<n; i++)
#define CLR(a, b) memset(a, b, sizeof(a))
#define debug puts("**debug**")
#define LL long long
#define PB push_back
#define MP make_pair
using namespace std;
const int maxn = 222;
int n, m, K, C, g[maxn][maxn];
struct BPM
{
int n, m; // 左右顶点个数
int G[maxn][maxn]; // 邻接表
int left[maxn]; // left[i]为右边第i个点的匹配点编号,-1表示不存在
bool T[maxn]; // T[i]为右边第i个点是否已标记
void init(int n, int m) {
this->n = n;
this->m = m;
memset(G, 0, sizeof(G));
}
inline void add(int u, int v)
{
G[u][v] = 1;
}
bool match(int u){
for(int v = 0; v < m; v++) if(G[u][v] && !T[v]) {
T[v] = true;
if (left[v] == -1 || match(left[v])){
left[v] = u;
return true;
}
}
return false;
}
// 求最大匹配
int solve() {
memset(left, -1, sizeof(left));
int ans = 0;
for(int u = 0; u < n; u++) { // 从左边结点u开始增广
memset(T, 0, sizeof(T));
if(match(u)) ans++;
}
return ans;
}
}solver;
bool ok(int time)
{
solver.init(n, m*2);
REP(i, n) REP(j, m)
{
if(g[i][j] <= time) solver.add(i, j);
if(g[i][j] + C <= time) solver.add(i, j+m);
}
return solver.solve() >= K;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
int L = 0, R = -1, M, ans;
scanf("%d%d%d%d", &n, &m, &K, &C);
REP(i, n) REP(j, m)
{
scanf("%d", &g[i][j]);
R = max(R, g[i][j]);
}
while(L <= R)
{
M = (L + R) >> 1;
if(ok(M)) ans = M, R = M - 1;
else L = M + 1;
}
printf("%d\n", ans);
}
return 0;
}