#include #include #include #include using namespace std; using ll = long long; // N, M は 100 までなので、105 確保 const int MAX_N = 105; const int MAX_M = 105; // INF は N+1 より大きければよい const int INF = MAX_N + 5; ll N, M, X, C; ll A[MAX_N][MAX_M]; // dp[i][j][k][j_start] // i: 位置 (0..N-1) // j: i のタイプ (0..M-1) // k: i-1 のタイプ (0..M-1) // j_start: 0 のタイプ (0..M-1) // 値: i で終わるタイプ j の最小連続長 int dp[MAX_N][MAX_M][MAX_M][MAX_M]; // DP高速化のためのヘルパー配列 // min_runs_p_eq_k[k][j_start] = min(dp[i-1][k][p][j_start]) (p == k の場合) // min_runs_p_neq_k[k][j_start] = min(dp[i-1][k][p][j_start]) (p != k の場合) int min_runs_p_eq_k[MAX_M][MAX_M]; int min_runs_p_neq_k[MAX_M][MAX_M]; // 美しさ K を達成できるか bool check(ll K) { // B[i][j]: (i, j) がペナルティなしで K 以上か // B_pen[i][j]: (i, j) がペナルティありで K 以上か bool B[MAX_N][MAX_M]; bool B_pen[MAX_N][MAX_M]; for (int i = 0; i < N; ++i) { for (int j = 0; j < M; ++j) { B[i][j] = (A[i][j] >= K); B_pen[i][j] = (A[i][j] - C >= K); } } // --- DPテーブル初期化 O(N M^3) --- for (int i = 0; i < N; ++i) { for (int j = 0; j < M; ++j) { for (int k = 0; k < M; ++k) { for (int js = 0; js < M; ++js) { dp[i][j][k][js] = INF; } } } } // --- 始点(0, 1) を固定 O(M^2) --- for (int j_start = 0; j_start < M; ++j_start) { for (int k_start = 0; k_start < M; ++k_start) { int start_run = (j_start == k_start) ? 2 : 1; if (start_run <= X) { // i=1, j=k_start, k=j_start, j_start=j_start dp[1][k_start][j_start][j_start] = start_run; } } } // --- DP実行 (i = 2 から N-1) O(N M^3) --- for (int i = 2; i < N; ++i) { // --- O(M^3) の前計算 (i-1 の行) --- for (int k = 0; k < M; ++k) { for (int js = 0; js < M; ++js) { min_runs_p_eq_k[k][js] = INF; min_runs_p_neq_k[k][js] = INF; for (int p = 0; p < M; ++p) { int run = dp[i - 1][k][p][js]; if (run > X) continue; if (k == p) { min_runs_p_eq_k[k][js] = min(min_runs_p_eq_k[k][js], run); } else { min_runs_p_neq_k[k][js] = min(min_runs_p_neq_k[k][js], run); } } } } // --- O(M^3) の遷移 --- for (int j = 0; j < M; ++j) { // i のタイプ for (int k = 0; k < M; ++k) { // i-1 のタイプ for (int js = 0; js < M; ++js) { // 0 のタイプ int best_run = INF; // (i-1)の美しさチェック // (i-1)の隣は(i-2)=p と (i)=j // Case 1: j != k (run length が 1 にリセット) if (j != k) { // (i-2)=p=k の場合 (ペナルティあり) if (min_runs_p_eq_k[k][js] <= X && B_pen[i - 1][k]) { best_run = 1; } // (i-2)=p!=k の場合 (ペナルティなし) if (min_runs_p_neq_k[k][js] <= X && B[i - 1][k]) { best_run = 1; } } // Case 2: j == k (run length が +1) else { // (i-1)の隣は(i-2)=p と (i)=j=k // (i-1)は p に関わらずペナルティ if (!B_pen[i - 1][k]) { continue; // この (j, k, js) の組合せは不可 } // (i-2)=p=k の場合 if (min_runs_p_eq_k[k][js] < X) { // +1 するので < X best_run = min(best_run, min_runs_p_eq_k[k][js] + 1); } // (i-2)=p!=k の場合 if (min_runs_p_neq_k[k][js] < X) { best_run = min(best_run, min_runs_p_neq_k[k][js] + 1); } } dp[i][j][k][js] = best_run; } } } } // DP loop (i) // --- 最終チェック O(M^4) --- // (j_start, k_start, j_final, k_final) を全探索 for (int j_start = 0; j_start < M; ++j_start) { for (int k_start = 0; k_start < M; ++k_start) { // (0, 1) の初期run lengthを再確認 int start_run_check = (j_start == k_start) ? 2 : 1; if (start_run_check > X) continue; for (int j_final = 0; j_final < M; ++j_final) { // N-1 のタイプ for (int k_final = 0; k_final < M; ++k_final) { // N-2 のタイプ int final_run = dp[N - 1][j_final][k_final][j_start]; if (final_run > X) continue; // (N-1) の美しさチェック // 隣は (N-2, k_final) と (0, j_start) bool penalized_N_1 = (j_final == k_final) || (j_final == j_start); if (penalized_N_1 && !B_pen[N - 1][j_final]) continue; if (!penalized_N_1 && !B[N - 1][j_final]) continue; // (0) の美しさチェック // 隣は (N-1, j_final) と (1, k_start) bool penalized_0 = (j_start == j_final) || (j_start == k_start); if (penalized_0 && !B_pen[0][j_start]) continue; if (!penalized_0 && !B[0][j_start]) continue; // すべてのチェックをパスした return true; } } } } return false; // M^4 通りの組合せすべてが失敗 } int main() { cin >> N >> M >> X >> C; ll max_a = 0; for (int i = 0; i < N; ++i) { for (int j = 0; j < M; ++j) { cin >> A[i][j]; max_a = max(max_a, A[i][j]); } } ll low = 0; ll high = max_a + 1; // 答えの最大値は Aij の最大値 ll ans = 0; // 二分探索 while (low <= high) { ll mid = low + (high - low) / 2; if (check(mid)) { ans = mid; // mid は達成可能 low = mid + 1; // もっと上を探す } else { high = mid - 1; // mid は達成不可能 } } cout << ans << endl; return 0; }